// The `Odin` file parser to be used in tooling.
package odin_parser

import "core:odin/ast"
import "core:odin/tokenizer"

import "core:fmt"

Warning_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any)
Error_Handler   :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any)

Flag :: enum u32 {
	Optional_Semicolons,
}

Flags :: distinct bit_set[Flag; u32]


Parser :: struct {
	file: ^ast.File,
	tok: tokenizer.Tokenizer,

	// If .Optional_Semicolons is true, semicolons are completely as statement terminators
	// different to .Insert_Semicolon in tok.flags
	flags: Flags,

	warn: Warning_Handler,
	err:  Error_Handler,

	prev_tok: tokenizer.Token,
	curr_tok: tokenizer.Token,

	// >= 0: In Expression
	// <  0: In Control Clause
	// NOTE(bill): Used to prevent type literals in control clauses
	expr_level:       int,
	allow_range:      bool, // NOTE(bill): Ranges are only allowed in certain cases
	allow_in_expr:    bool, // NOTE(bill): in expression are only allowed in certain cases
	in_foreign_block: bool,
	allow_type:       bool,

	lead_comment: ^ast.Comment_Group,
	line_comment: ^ast.Comment_Group,

	curr_proc: ^ast.Node,

	error_count: int,

	fix_count: int,
	fix_prev_pos: tokenizer.Pos,

	peeking: bool,
}

MAX_FIX_COUNT :: 10

Stmt_Allow_Flag :: enum {
	In,
	Label,
}
Stmt_Allow_Flags :: distinct bit_set[Stmt_Allow_Flag]


Import_Decl_Kind :: enum {
	Standard,
	Using,
}



default_warning_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) {
	fmt.eprintf("%s(%d:%d): Warning: ", pos.file, pos.line, pos.column)
	fmt.eprintf(msg, ..args)
	fmt.eprintf("\n")
}
default_error_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) {
	fmt.eprintf("%s(%d:%d): ", pos.file, pos.line, pos.column)
	fmt.eprintf(msg, ..args)
	fmt.eprintf("\n")
}

warn :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, args: ..any) {
	if p.warn != nil {
		p.warn(pos, msg, ..args)
	}
	p.file.syntax_warning_count += 1
}

error :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, args: ..any) {
	if p.err != nil {
		p.err(pos, msg, ..args)
	}
	p.file.syntax_error_count += 1
	p.error_count += 1
}


end_pos :: proc(tok: tokenizer.Token) -> tokenizer.Pos {
	pos := tok.pos
	pos.offset += len(tok.text)

	if (tok.kind == .Comment && tok.text[:2] == "/*") || (tok.kind == .String && tok.text[:1] == "`") {
		for i := 0; i < len(tok.text); i += 1 {
			c := tok.text[i]
			if c == '\n' {
				pos.line += 1
				pos.column = 1
			} else {
				pos.column += 1
			}
		}
	} else {
		pos.column += len(tok.text)
	}
	return pos
}

default_parser :: proc(flags := Flags{.Optional_Semicolons}) -> Parser {
	return Parser {
		flags = flags,
		err  = default_error_handler,
		warn = default_warning_handler,
	}
}

is_package_name_reserved :: proc(name: string) -> bool {
	switch name {
	case "builtin", "intrinsics":
		return true
	}
	return false
}

parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool {
	zero_parser: {
		p.prev_tok         = {}
		p.curr_tok         = {}
		p.expr_level       = 0
		p.allow_range      = false
		p.allow_in_expr    = false
		p.in_foreign_block = false
		p.allow_type       = false
		p.lead_comment     = nil
		p.line_comment     = nil
	}

	p.tok.flags += {.Insert_Semicolon}

	p.file = file
	tokenizer.init(&p.tok, file.src, file.fullpath, p.err)
	if p.tok.ch <= 0 {
		return true
	}


	advance_token(p)
	consume_comment_groups(p, p.prev_tok)

	docs := p.lead_comment

	invalid_pre_package_token: Maybe(tokenizer.Token)

	for p.curr_tok.kind != .Package && p.curr_tok.kind != .EOF {
		if p.curr_tok.kind == .Comment {
			consume_comment_groups(p, p.prev_tok)
		} else if p.curr_tok.kind == .File_Tag {
			append(&p.file.tags, p.curr_tok)
			advance_token(p)
		} else {
			if invalid_pre_package_token == nil {
				invalid_pre_package_token = p.curr_tok
			}

			advance_token(p)
		}
	}

	if p.curr_tok.kind != .Package {
		t := invalid_pre_package_token.? or_else p.curr_tok
		error(p, t.pos, "Expected a package declaration at the start of the file")
		return false
	}
	
	p.file.pkg_token = expect_token(p, .Package)
	
	if ippt, ok := invalid_pre_package_token.?; ok {
		error(p, ippt.pos, "Expected only comments or lines starting with '#+' before the package declaration")
		return false
	}
	
	pkg_name := expect_token_after(p, .Ident, "package")
	if pkg_name.kind == .Ident {
		switch name := pkg_name.text; {
		case is_blank_ident(name):
			error(p, pkg_name.pos, "invalid package name '_'")
		case is_package_name_reserved(name), file.pkg != nil && file.pkg.kind != .Runtime && name == "runtime":
			error(p, pkg_name.pos, "use of reserved package name '%s'", name)
		}
	}
	p.file.pkg_name = pkg_name.text

	pd := ast.new(ast.Package_Decl, pkg_name.pos, end_pos(p.prev_tok))
	pd.docs    = docs
	pd.token   = p.file.pkg_token
	pd.name    = pkg_name.text
	pd.comment = p.line_comment
	p.file.pkg_decl = pd
	p.file.docs = docs

	expect_semicolon(p, pd)

	if p.file.syntax_error_count > 0 {
		return false
	}

	p.file.decls = make([dynamic]^ast.Stmt)

	for p.curr_tok.kind != .EOF {
		stmt := parse_stmt(p)
		if stmt != nil {
			if _, ok := stmt.derived.(^ast.Empty_Stmt); !ok {
				append(&p.file.decls, stmt)
				if es, es_ok := stmt.derived.(^ast.Expr_Stmt); es_ok && es.expr != nil {
					if _, pl_ok := es.expr.derived.(^ast.Proc_Lit); pl_ok {
						error(p, stmt.pos, "procedure literal evaluated but not used")
					}
				}
			}
		}
	}

	return true
}

peek_token_kind :: proc(p: ^Parser, kind: tokenizer.Token_Kind, lookahead := 0) -> (ok: bool) {
	prev_parser := p^
	p.peeking = true

	defer {
		p^ = prev_parser
		p.peeking = false
	}

	p.tok.err = nil
	for i := 0; i <= lookahead; i += 1 {
		advance_token(p)
	}
	ok = p.curr_tok.kind == kind

	return
}

peek_token :: proc(p: ^Parser, lookahead := 0) -> (tok: tokenizer.Token) {
	prev_parser := p^
	p.peeking = true

	defer {
		p^ = prev_parser
		p.peeking = false
	}

	p.tok.err = nil
	for i := 0; i <= lookahead; i += 1 {
		advance_token(p)
	}
	tok = p.curr_tok
	return
}
skip_possible_newline :: proc(p: ^Parser) -> bool {
	if tokenizer.is_newline(p.curr_tok) {
		advance_token(p)
		return true
	}
	return false
}

skip_possible_newline_for_literal :: proc(p: ^Parser) -> bool {
	if .Optional_Semicolons not_in p.flags {
		return false
	}

	curr_pos := p.curr_tok.pos
	if tokenizer.is_newline(p.curr_tok) {
		next := peek_token(p)
		if curr_pos.line+1 >= next.pos.line {
			#partial switch next.kind {
			case .Open_Brace, .Else, .Where:
				advance_token(p)
				return true
			}
		}
	}

	return false
}


next_token0 :: proc(p: ^Parser) -> bool {
	p.curr_tok = tokenizer.scan(&p.tok)
	if p.curr_tok.kind == .EOF {
		// error(p, p.curr_tok.pos, "token is EOF");
		return false
	}
	return true
}

consume_comment :: proc(p: ^Parser) -> (tok: tokenizer.Token, end_line: int) {
	tok = p.curr_tok
	assert(tok.kind == .Comment)
	end_line = tok.pos.line

	if tok.text[1] == '*' {
		for c in tok.text {
			if c == '\n' {
				end_line += 1
			}
		}
	}

	_ = next_token0(p)
	if p.curr_tok.pos.line > tok.pos.line {
		end_line += 1
	}

	return
}

consume_comment_group :: proc(p: ^Parser, n: int) -> (comments: ^ast.Comment_Group, end_line: int) {
	list: [dynamic]tokenizer.Token
	end_line = p.curr_tok.pos.line
	for p.curr_tok.kind == .Comment &&
	    p.curr_tok.pos.line <= end_line+n {
		comment: tokenizer.Token
		comment, end_line = consume_comment(p)
		append(&list, comment)
	}

	if len(list) > 0 && !p.peeking {
		comments = ast.new(ast.Comment_Group, list[0].pos, end_pos(list[len(list)-1]))
		comments.list = list[:]
		append(&p.file.comments, comments)
	}

	return
}

consume_comment_groups :: proc(p: ^Parser, prev: tokenizer.Token) {
	if p.curr_tok.kind != .Comment {
		return
	}
	comment: ^ast.Comment_Group
	end_line := 0

	if p.curr_tok.pos.line == prev.pos.line {
		comment, end_line = consume_comment_group(p, 0)
		if p.curr_tok.pos.line != end_line ||
		   p.curr_tok.pos.line == prev.pos.line+1 ||
		   p.curr_tok.kind == .EOF {
			p.line_comment = comment
		}
	}

	end_line = -1
	for p.curr_tok.kind == .Comment {
		comment, end_line = consume_comment_group(p, 1)
	}
	if end_line+1 >= p.curr_tok.pos.line || end_line < 0 {
		p.lead_comment = comment
	}

	assert(p.curr_tok.kind != .Comment)
}

advance_token :: proc(p: ^Parser) -> tokenizer.Token {
	p.lead_comment = nil
	p.line_comment = nil
	p.prev_tok = p.curr_tok
	prev := p.prev_tok

	if next_token0(p) {
		consume_comment_groups(p, prev)
	}
	return prev
}

expect_token :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> tokenizer.Token {
	prev := p.curr_tok
	if prev.kind != kind {
		e := tokenizer.to_string(kind)
		g := tokenizer.token_to_string(prev)
		error(p, prev.pos, "expected '%s', got '%s'", e, g)
	}
	advance_token(p)
	return prev
}

expect_token_after :: proc(p: ^Parser, kind: tokenizer.Token_Kind, msg: string) -> tokenizer.Token {
	prev := p.curr_tok
	if prev.kind != kind {
		e := tokenizer.to_string(kind)
		g := tokenizer.token_to_string(prev)
		error(p, prev.pos, "expected '%s' after %s, got '%s'", e, msg, g)
	}
	advance_token(p)
	return prev
}

expect_operator :: proc(p: ^Parser) -> tokenizer.Token {
	prev := p.curr_tok
	#partial switch prev.kind {
	case .If, .When, .Or_Else:
		// okay
	case:
		if !tokenizer.is_operator(prev.kind) {
			g := tokenizer.token_to_string(prev)
			error(p, prev.pos, "expected an operator, got '%s'", g)
		}
	}
	advance_token(p)
	return prev
}

allow_token :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> bool {
	if p.curr_tok.kind == kind {
		advance_token(p)
		return true
	}
	return false
}

end_of_line_pos :: proc(p: ^Parser, tok: tokenizer.Token) -> tokenizer.Pos {
	offset := clamp(tok.pos.offset, 0, len(p.tok.src)-1)
	s := p.tok.src[offset:]
	pos := tok.pos
	pos.column -= 1
	for len(s) != 0 && s[0] != 0 && s[0] != '\n' {
		s = s[1:]
		pos.column += 1
	}
	return pos
}

expect_closing_brace_of_field_list :: proc(p: ^Parser) -> tokenizer.Token {
	return expect_closing_token_of_field_list(p, .Close_Brace, "field list")
}

expect_closing_token_of_field_list :: proc(p: ^Parser, closing_kind: tokenizer.Token_Kind, msg: string) -> tokenizer.Token {
	token := p.curr_tok
	if allow_token(p, closing_kind) {
		return token
	}
	if allow_token(p, .Semicolon) && !tokenizer.is_newline(token) {
		str := tokenizer.token_to_string(token)
		error(p, end_of_line_pos(p, p.prev_tok), "expected a comma, got %s", str)
	}
	expect_closing := expect_token_after(p, closing_kind, msg)

	if expect_closing.kind != closing_kind {
		for p.curr_tok.kind != closing_kind && p.curr_tok.kind != .EOF && !is_non_inserted_semicolon(p.curr_tok) {
			advance_token(p)
		}
		return p.curr_tok
	} 

	return expect_closing
}

expect_closing_parentheses_of_field_list :: proc(p: ^Parser) -> tokenizer.Token {
	token := p.curr_tok
	if allow_token(p, .Close_Paren) {
		return token
	}

	if allow_token(p, .Semicolon) && !tokenizer.is_newline(token) {
		str := tokenizer.token_to_string(token)
		error(p, end_of_line_pos(p, p.prev_tok), "expected a comma, got %s", str)
	}

	for p.curr_tok.kind != .Close_Paren && p.curr_tok.kind != .EOF && !is_non_inserted_semicolon(p.curr_tok) {
		advance_token(p)
	}

	return expect_token(p, .Close_Paren)
}

is_non_inserted_semicolon :: proc(tok: tokenizer.Token) -> bool {
	return tok.kind == .Semicolon && tok.text != "\n"
}

is_blank_ident :: proc{
	is_blank_ident_string,
	is_blank_ident_token,
	is_blank_ident_node,
}
is_blank_ident_string :: proc(str: string) -> bool {
	return str == "_"
}
is_blank_ident_token :: proc(tok: tokenizer.Token) -> bool {
	if tok.kind == .Ident {
		return is_blank_ident_string(tok.text)
	}
	return false
}
is_blank_ident_node :: proc(node: ^ast.Node) -> bool {
	if ident, ok := node.derived.(^ast.Ident); ok {
		return is_blank_ident(ident.name)
	}
	return true
}

fix_advance_to_next_stmt :: proc(p: ^Parser) {
	for {
		#partial switch t := p.curr_tok; t.kind {
		case .EOF, .Semicolon:
			return

		case .Package, .Foreign, .Import,
		     .If, .For, .When, .Return, .Switch,
		     .Defer, .Using,
		     .Break, .Continue, .Fallthrough,
		     .Hash:


			if t.pos == p.fix_prev_pos && p.fix_count < MAX_FIX_COUNT {
				p.fix_count += 1
				return
			}
			if t.pos.offset < p.fix_prev_pos.offset {
				p.fix_prev_pos = t.pos
				p.fix_count = 0
				return
			}
		}
		advance_token(p)
	}
}


is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
	if node == nil {
		return false
	}

	if .Optional_Semicolons in p.flags {
		return true
	}

	#partial switch n in node.derived {
	case ^ast.Empty_Stmt, ^ast.Block_Stmt:
		return true

	case ^ast.If_Stmt, ^ast.When_Stmt,
	     ^ast.For_Stmt, ^ast.Range_Stmt, ^ast.Inline_Range_Stmt,
	     ^ast.Switch_Stmt, ^ast.Type_Switch_Stmt:
		return true

	case ^ast.Helper_Type:
		return is_semicolon_optional_for_node(p, n.type)
	case ^ast.Distinct_Type:
		return is_semicolon_optional_for_node(p, n.type)
	case ^ast.Pointer_Type:
		return is_semicolon_optional_for_node(p, n.elem)
	case ^ast.Struct_Type, ^ast.Union_Type, ^ast.Enum_Type, ^ast.Bit_Set_Type, ^ast.Bit_Field_Type:
		// Require semicolon within a procedure body
		return p.curr_proc == nil
	case ^ast.Proc_Lit:
		return true

	case ^ast.Package_Decl, ^ast.Import_Decl, ^ast.Foreign_Import_Decl:
		return true

	case ^ast.Foreign_Block_Decl:
		return is_semicolon_optional_for_node(p, n.body)

	case ^ast.Value_Decl:
		if n.is_mutable {
			return false
		}
		if len(n.values) > 0 {
			return is_semicolon_optional_for_node(p, n.values[len(n.values)-1])
		}
	}

	return false
}

expect_semicolon_newline_error :: proc(p: ^Parser, token: tokenizer.Token, s: ^ast.Node) {
	if .Optional_Semicolons not_in p.flags && .Insert_Semicolon in p.tok.flags && token.text == "\n" {
		#partial switch token.kind {
		case .Close_Brace:
		case .Close_Paren:
		case .Else:
			return
		}
		if is_semicolon_optional_for_node(p, s) {
			return
		}

		tok := token
		tok.pos.column -= 1
		error(p, tok.pos, "expected ';', got newline")
	}
}


expect_semicolon :: proc(p: ^Parser, node: ^ast.Node) -> bool {
	if allow_token(p, .Semicolon) {
		expect_semicolon_newline_error(p, p.prev_tok, node)
		return true
	}

	prev := p.prev_tok
	if prev.kind == .Semicolon {
		expect_semicolon_newline_error(p, p.prev_tok, node)
		return true
	}

	if p.curr_tok.kind == .EOF {
		return true
	}

	if node != nil {
		if .Insert_Semicolon in p.tok.flags  {
			#partial switch p.curr_tok.kind {
			case .Close_Brace, .Close_Paren, .Else, .EOF:
				return true
			}

			if is_semicolon_optional_for_node(p, node) {
				return true
			}
		} else if prev.pos.line != p.curr_tok.pos.line {
			if is_semicolon_optional_for_node(p, node) {
				return true
			}
		} else {
			#partial switch p.curr_tok.kind {
			case .Close_Brace, .Close_Paren, .Else:
				return true
			case .EOF:
				if is_semicolon_optional_for_node(p, node) {
					return true
				}
			}
		}
	} else {
		if p.curr_tok.kind == .EOF {
			return true
		}
	}

	error(p, prev.pos, "expected ';', got %s", tokenizer.token_to_string(p.curr_tok))
	fix_advance_to_next_stmt(p)
	return false
}

new_blank_ident :: proc(p: ^Parser, pos: tokenizer.Pos) -> ^ast.Ident {
	tok: tokenizer.Token
	tok.pos = pos
	i := ast.new(ast.Ident, pos, end_pos(tok))
	i.name = "_"
	return i
}

parse_ident :: proc(p: ^Parser) -> ^ast.Ident {
	tok := p.curr_tok
	pos := tok.pos
	name := "_"
	if tok.kind == .Ident {
		name = tok.text
		advance_token(p)
	} else {
		expect_token(p, .Ident)
	}
	i := ast.new(ast.Ident, pos, end_pos(tok))
	i.name = name
	return i
}

parse_stmt_list :: proc(p: ^Parser) -> []^ast.Stmt {
	list: [dynamic]^ast.Stmt
	for p.curr_tok.kind != .Case &&
	    p.curr_tok.kind != .Close_Brace &&
	    p.curr_tok.kind != .EOF  {
		stmt := parse_stmt(p)
		if stmt != nil {
			if _, ok := stmt.derived.(^ast.Empty_Stmt); !ok {
				append(&list, stmt)
				if es, es_ok := stmt.derived.(^ast.Expr_Stmt); es_ok && es.expr != nil {
					if _, pl_ok := es.expr.derived.(^ast.Proc_Lit); pl_ok {
						error(p, stmt.pos, "procedure literal evaluated but not used")
					}
				}
			}
		}
	}
	return list[:]
}

parse_block_stmt :: proc(p: ^Parser, is_when: bool) -> ^ast.Stmt {
	skip_possible_newline_for_literal(p)
	if !is_when && p.curr_proc == nil {
		error(p, p.curr_tok.pos, "you cannot use a block statement in the file scope")
	}
	return parse_body(p)
}

parse_when_stmt :: proc(p: ^Parser) -> ^ast.When_Stmt {
	tok := expect_token(p, .When)

	cond: ^ast.Expr
	body: ^ast.Stmt
	else_stmt: ^ast.Stmt

	prev_level := p.expr_level
	p.expr_level = -1
	prev_allow_in_expr := p.allow_in_expr
	p.allow_in_expr = true

	cond = parse_expr(p, false)

	p.allow_in_expr = prev_allow_in_expr
	p.expr_level = prev_level

	if cond == nil {
		error(p, p.curr_tok.pos, "expected a condition for when statement")
	}
	if allow_token(p, .Do) {
		body = convert_stmt_to_body(p, parse_stmt(p))
		if cond.pos.line != body.pos.line {
			error(p, body.pos, "the body of a 'do' must be on the same line as when statement")
		}
	} else {
		body = parse_block_stmt(p, true)
	}

	skip_possible_newline_for_literal(p)
	if p.curr_tok.kind == .Else {
		else_tok := expect_token(p, .Else)
		#partial switch p.curr_tok.kind {
		case .When:
			else_stmt = parse_when_stmt(p)
		case .Open_Brace:
			else_stmt = parse_block_stmt(p, true)
		case .Do:
			expect_token(p, .Do)
			else_stmt = convert_stmt_to_body(p, parse_stmt(p))
			if else_tok.pos.line != else_stmt.pos.line {
				error(p, else_stmt.pos, "the body of a 'do' must be on the same line as 'else'")
			}
		case:
			error(p, p.curr_tok.pos, "expected when statement block statement")
			else_stmt = ast.new(ast.Bad_Stmt, p.curr_tok.pos, end_pos(p.curr_tok))
		}
	}

	end := body.end
	if else_stmt != nil {
		end = else_stmt.end
	}
	when_stmt := ast.new(ast.When_Stmt, tok.pos, end)
	when_stmt.when_pos  = tok.pos
	when_stmt.cond      = cond
	when_stmt.body      = body
	when_stmt.else_stmt = else_stmt
	return when_stmt
}

convert_stmt_to_expr :: proc(p: ^Parser, stmt: ^ast.Stmt, kind: string) -> ^ast.Expr {
	if stmt == nil {
		return nil
	}
	if es, ok := stmt.derived.(^ast.Expr_Stmt); ok {
		return es.expr
	}
	error(p, stmt.pos, "expected %s, found a simple statement", kind)
	return ast.new(ast.Bad_Expr, p.curr_tok.pos, end_pos(p.curr_tok))
}

parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt {
	tok := expect_token(p, .If)

	init: ^ast.Stmt
	cond: ^ast.Expr
	body: ^ast.Stmt
	else_stmt: ^ast.Stmt

	prev_level := p.expr_level
	p.expr_level = -1
	prev_allow_in_expr := p.allow_in_expr
	p.allow_in_expr = true
	if allow_token(p, .Semicolon) {
		cond = parse_expr(p, false)
	} else {
		init = parse_simple_stmt(p, nil)
		if parse_control_statement_semicolon_separator(p) {
			cond = parse_expr(p, false)
		} else {
			cond = convert_stmt_to_expr(p, init, "boolean expression")
			init = nil
		}
	}

	p.expr_level = prev_level
	p.allow_in_expr = prev_allow_in_expr

	if cond == nil {
		error(p, p.curr_tok.pos, "expected a condition for if statement")

	}
	if allow_token(p, .Do) {
		body = convert_stmt_to_body(p, parse_stmt(p))
		if cond.pos.line != body.pos.line {
			error(p, body.pos, "the body of a 'do' must be on the same line as the if condition")
		}
	} else {
		body = parse_block_stmt(p, false)
	}

	else_tok := p.curr_tok.pos

	skip_possible_newline_for_literal(p)
	if p.curr_tok.kind == .Else {
		else_tok := expect_token(p, .Else)
		#partial switch p.curr_tok.kind {
		case .If:
			else_stmt = parse_if_stmt(p)
		case .Open_Brace:
			else_stmt = parse_block_stmt(p, false)
		case .Do:
			expect_token(p, .Do)
			else_stmt = convert_stmt_to_body(p, parse_stmt(p))
			if else_tok.pos.line != else_stmt.pos.line {
				error(p, body.pos, "the body of a 'do' must be on the same line as 'else'")
			}
		case:
			error(p, p.curr_tok.pos, "expected if statement block statement")
			else_stmt = ast.new(ast.Bad_Stmt, p.curr_tok.pos, end_pos(p.curr_tok))
		}
	}
	
	end: tokenizer.Pos
	if body != nil {
		end = body.end
	}
	if else_stmt != nil {
		end = else_stmt.end
	}
	if_stmt := ast.new(ast.If_Stmt, tok.pos, end)
	if_stmt.if_pos  = tok.pos
	if_stmt.init      = init
	if_stmt.cond      = cond
	if_stmt.body      = body
	if_stmt.else_stmt = else_stmt
	if_stmt.else_pos = else_tok
	return if_stmt
}

parse_control_statement_semicolon_separator :: proc(p: ^Parser) -> bool {
	tok := peek_token(p)
	if tok.kind != .Open_Brace {
		return allow_token(p, .Semicolon)
	}
	if p.curr_tok.text == ";" {
		return allow_token(p, .Semicolon)
	}
	return false

}

parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
	if p.curr_proc == nil {
		error(p, p.curr_tok.pos, "you cannot use a for statement in the file scope")
	}

	tok := expect_token(p, .For)

	init: ^ast.Stmt
	cond: ^ast.Stmt
	post: ^ast.Stmt
	body: ^ast.Stmt
	is_range := false

	if p.curr_tok.kind != .Open_Brace && p.curr_tok.kind != .Do {
		prev_level := p.expr_level
		defer p.expr_level = prev_level
		p.expr_level = -1

		if p.curr_tok.kind == .In {
			in_tok := expect_token(p, .In)
			rhs: ^ast.Expr

			prev_allow_range := p.allow_range
			p.allow_range = true
			rhs = parse_expr(p, false)
			p.allow_range = prev_allow_range

			if allow_token(p, .Do) {
				body = convert_stmt_to_body(p, parse_stmt(p))
				if tok.pos.line != body.pos.line {
					error(p, body.pos, "the body of a 'do' must be on the same line as 'else'")
				}

			} else {
				body = parse_body(p)
			}

			range_stmt := ast.new(ast.Range_Stmt, tok.pos, body)
			range_stmt.for_pos = tok.pos
			range_stmt.in_pos = in_tok.pos
			range_stmt.expr = rhs
			range_stmt.body = body
			return range_stmt
		}

		if p.curr_tok.kind != .Semicolon {
			cond = parse_simple_stmt(p, {Stmt_Allow_Flag.In})
			if as, ok := cond.derived.(^ast.Assign_Stmt); ok && as.op.kind == .In {
				is_range = true
			}
		}

		if !is_range && parse_control_statement_semicolon_separator(p) {
			init = cond
			cond = nil


			if p.curr_tok.kind == .Open_Brace || p.curr_tok.kind == .Do {
				error(p, p.curr_tok.pos, "Expected ';', followed by a condition expression and post statement, got %s", tokenizer.tokens[p.curr_tok.kind])
			} else {
				if p.curr_tok.kind != .Semicolon {
					cond = parse_simple_stmt(p, nil)
				}

				if p.curr_tok.text != ";" {
					error(p, p.curr_tok.pos, "Expected ';', got %s", tokenizer.token_to_string(p.curr_tok))
				} else {
					expect_semicolon(p, nil)
				}

				if p.curr_tok.kind != .Open_Brace && p.curr_tok.kind != .Do {
					post = parse_simple_stmt(p, nil)
				}
			}
		}
	}

	if allow_token(p, .Do) {
		body = convert_stmt_to_body(p, parse_stmt(p))
		if tok.pos.line != body.pos.line {
			error(p, body.pos, "the body of a 'do' must be on the same line as the 'for' token")
		}
	} else {
		allow_token(p, .Semicolon)
		body = parse_body(p)
	}


	if is_range {
		assign_stmt := cond.derived.(^ast.Assign_Stmt)
		vals := assign_stmt.lhs[:]

		rhs: ^ast.Expr
		if len(assign_stmt.rhs) > 0 {
			rhs = assign_stmt.rhs[0]
		}

		range_stmt := ast.new(ast.Range_Stmt, tok.pos, body)
		range_stmt.for_pos = tok.pos
		range_stmt.vals = vals
		range_stmt.in_pos = assign_stmt.op.pos
		range_stmt.expr = rhs
		range_stmt.body = body
		return range_stmt
	}

	cond_expr := convert_stmt_to_expr(p, cond, "boolean expression")
	for_stmt := ast.new(ast.For_Stmt, tok.pos, body)
	for_stmt.for_pos = tok.pos
	for_stmt.init = init
	for_stmt.cond = cond_expr
	for_stmt.post = post
	for_stmt.body = body
	return for_stmt
}

parse_case_clause :: proc(p: ^Parser, is_type_switch: bool) -> ^ast.Case_Clause {
	tok := expect_token(p, .Case)

	list: []^ast.Expr

	if p.curr_tok.kind != .Colon {
		prev_allow_range, prev_allow_in_expr := p.allow_range, p.allow_in_expr
		defer p.allow_range, p.allow_in_expr = prev_allow_range, prev_allow_in_expr
		p.allow_range, p.allow_in_expr = !is_type_switch, !is_type_switch

		list = parse_rhs_expr_list(p)
	}

	terminator := expect_token(p, .Colon)

	stmts := parse_stmt_list(p)

	cc := ast.new(ast.Case_Clause, tok.pos, end_pos(p.prev_tok))
	cc.list = list
	cc.terminator = terminator
	cc.body = stmts
	cc.case_pos = tok.pos
	return cc
}

parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
	tok := expect_token(p, .Switch)

	init: ^ast.Stmt
	tag:  ^ast.Stmt
	is_type_switch := false
	clauses: [dynamic]^ast.Stmt

	if p.curr_tok.kind != .Open_Brace {
		prev_level := p.expr_level
		defer p.expr_level = prev_level
		p.expr_level = -1

		if p.curr_tok.kind == .In {
			in_tok := expect_token(p, .In)
			is_type_switch = true

			lhs := make([]^ast.Expr, 1)
			rhs := make([]^ast.Expr, 1)
			lhs[0] = new_blank_ident(p, tok.pos)
			rhs[0] = parse_expr(p, true)

			as := ast.new(ast.Assign_Stmt, tok.pos, rhs[0])
			as.lhs = lhs
			as.op  = in_tok
			as.rhs = rhs
			tag = as
		} else {
			tag = parse_simple_stmt(p, {Stmt_Allow_Flag.In})
			if as, ok := tag.derived.(^ast.Assign_Stmt); ok && as.op.kind == .In {
				is_type_switch = true
			} else if parse_control_statement_semicolon_separator(p) {
				init = tag
				tag = nil
				if p.curr_tok.kind != .Open_Brace {
					tag = parse_simple_stmt(p, nil)
				}
			}
		}
	}


	skip_possible_newline(p)
	open := expect_token(p, .Open_Brace)

	for p.curr_tok.kind == .Case {
		clause := parse_case_clause(p, is_type_switch)
		append(&clauses, clause)
	}

	close := expect_token(p, .Close_Brace)

	body := ast.new(ast.Block_Stmt, open.pos, end_pos(close))
	body.stmts = clauses[:]

	if is_type_switch {
		ts := ast.new(ast.Type_Switch_Stmt, tok.pos, body)
		ts.tag  = tag
		ts.body = body
		ts.switch_pos = tok.pos
		return ts
	} else {
		cond := convert_stmt_to_expr(p, tag, "switch expression")
		ts := ast.new(ast.Switch_Stmt, tok.pos, body)
		ts.init = init
		ts.cond = cond
		ts.body = body
		ts.switch_pos = tok.pos
		return ts
	}
}

parse_attribute :: proc(p: ^Parser, tok: tokenizer.Token, open_kind, close_kind: tokenizer.Token_Kind, docs: ^ast.Comment_Group) -> ^ast.Stmt {
	elems: [dynamic]^ast.Expr

	open, close: tokenizer.Token

	if p.curr_tok.kind == .Ident {
		elem := parse_ident(p)
		append(&elems, elem)
	} else {
		open = expect_token(p, open_kind)
		p.expr_level += 1
		for p.curr_tok.kind != close_kind &&
			p.curr_tok.kind != .EOF {
			elem: ^ast.Expr
			elem = parse_ident(p)
			if p.curr_tok.kind == .Eq {
				eq := expect_token(p, .Eq)
				value := parse_value(p)
				fv := ast.new(ast.Field_Value, elem.pos, value)
				fv.field = elem
				fv.sep   = eq.pos
				fv.value = value

				elem = fv
			}
			append(&elems, elem)

			allow_token(p, .Comma) or_break
		}
		p.expr_level -= 1
		close = expect_token_after(p, close_kind, "attribute")
	}

	attribute := ast.new(ast.Attribute, tok.pos, end_pos(close))
	attribute.tok   = tok.kind
	attribute.open  = open.pos
	attribute.elems = elems[:]
	attribute.close = close.pos

	skip_possible_newline(p)

	decl := parse_stmt(p)
	#partial switch d in decl.derived_stmt {
	case ^ast.Value_Decl:
		if d.docs == nil { d.docs = docs }
		append(&d.attributes, attribute)
	case ^ast.Foreign_Block_Decl:
		if d.docs == nil { d.docs = docs }
		append(&d.attributes, attribute)
	case ^ast.Foreign_Import_Decl:
		if d.docs == nil { d.docs = docs }
		append(&d.attributes, attribute)
	case ^ast.Import_Decl:
		if d.docs == nil { d.docs = docs }
		append(&d.attributes, attribute)
	case:
		error(p, decl.pos, "expected a value or foreign declaration after an attribute")
		free(attribute)
		delete(elems)
	}
	return decl

}

parse_foreign_block_decl :: proc(p: ^Parser) -> ^ast.Stmt {
	decl := parse_stmt(p)
	#partial switch _ in decl.derived_stmt {
	case ^ast.Empty_Stmt, ^ast.Bad_Stmt, ^ast.Bad_Decl:
		// Ignore
		return nil
	case ^ast.When_Stmt, ^ast.Value_Decl:
		return decl
	}

	error(p, decl.pos, "foreign blocks only allow procedure and variable declarations")

	return nil

}

parse_foreign_block :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Foreign_Block_Decl {
	docs := p.lead_comment

	foreign_library: ^ast.Expr
	#partial switch p.curr_tok.kind {
	case .Open_Brace:
		i := ast.new(ast.Ident, tok.pos, end_pos(tok))
		i.name = "_"
		foreign_library = i
	case:
		foreign_library = parse_ident(p)
	}

	decls: [dynamic]^ast.Stmt

	prev_in_foreign_block := p.in_foreign_block
	defer p.in_foreign_block = prev_in_foreign_block
	p.in_foreign_block = true

	skip_possible_newline_for_literal(p)
	open := expect_token(p, .Open_Brace)
	for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
		decl := parse_foreign_block_decl(p)
		if decl != nil {
			append(&decls, decl)
		}
	}
	close := expect_token(p, .Close_Brace)

	body := ast.new(ast.Block_Stmt, open.pos, end_pos(close))
	body.open = open.pos
	body.stmts = decls[:]
	body.close = close.pos

	decl := ast.new(ast.Foreign_Block_Decl, tok.pos, body)
	decl.docs            = docs
	decl.tok             = tok
	decl.foreign_library = foreign_library
	decl.body            = body
	return decl
}


parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl {
	docs := p.lead_comment
	tok := expect_token(p, .Foreign)

	#partial switch p.curr_tok.kind {
	case .Ident, .Open_Brace:
		return parse_foreign_block(p, tok)

	case .Import:
		import_tok := expect_token(p, .Import)
		name: ^ast.Ident
		if p.curr_tok.kind == .Ident {
			name = parse_ident(p)
		}

		if name != nil && is_blank_ident(name) {
			error(p, name.pos, "illegal foreign import name: '_'")
		}

		fullpaths: [dynamic]^ast.Expr
		if allow_token(p, .Open_Brace) {
			for p.curr_tok.kind != .Close_Brace &&
				p.curr_tok.kind != .EOF {
				path := parse_expr(p, false)
				append(&fullpaths, path)

				allow_token(p, .Comma) or_break
			}
			expect_token(p, .Close_Brace)
		} else {
			path := expect_token(p, .String)
			reserve(&fullpaths, 1)
			bl := ast.new(ast.Basic_Lit, path.pos, end_pos(path))
			bl.tok = path
			append(&fullpaths, bl)
		}

		if len(fullpaths) == 0 {
			error(p, import_tok.pos, "foreign import without any paths")
		}

		decl := ast.new(ast.Foreign_Import_Decl, tok.pos, end_pos(p.prev_tok))
		decl.docs            = docs
		decl.foreign_tok     = tok
		decl.import_tok      = import_tok
		decl.name            = name
		decl.fullpaths       = fullpaths[:]
		expect_semicolon(p, decl)
		decl.comment = p.line_comment
		return decl
	}

	error(p, tok.pos, "invalid foreign declaration")
	return ast.new(ast.Bad_Decl, tok.pos, end_pos(tok))
}


parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast.Stmt {
	val0, val1: ^ast.Expr
	in_tok: tokenizer.Token
	expr: ^ast.Expr
	body: ^ast.Stmt
	args: [dynamic]^ast.Expr

	if allow_token(p, .Open_Paren) {
		p.expr_level += 1
		if p.curr_tok.kind == .Close_Paren {
			error(p, p.curr_tok.pos, "#unroll expected at least 1 argument, got 0")
		} else {
			args = make([dynamic]^ast.Expr)
			for p.curr_tok.kind != .Close_Paren &&
			    p.curr_tok.kind != .EOF {
				arg := parse_value(p)

				if p.curr_tok.kind == .Eq {
					eq := expect_token(p, .Eq)
					if arg != nil {
						if _, ok := arg.derived.(^ast.Ident); !ok {
							error(p, arg.pos, "expected an identifier for 'key=value'")
						}
					}
					value := parse_value(p)
					fv := ast.new(ast.Field_Value, arg.pos, value)
					fv.field = arg
					fv.sep   = eq.pos
					fv.value = value

					arg = fv
				}

				append(&args, arg)

				allow_token(p, .Comma) or_break
			}
		}

		p.expr_level -= 1
		_ = expect_token_after(p, .Close_Paren, "#unroll")
	}

	for_tok := expect_token(p, .For)

	bad_stmt := false

	if p.curr_tok.kind != .In {
		idents := parse_ident_list(p, false)
		switch len(idents) {
		case 1:
			val0 = idents[0]
		case 2:
			val0, val1 = idents[0], idents[1]
		case:
			error(p, for_tok.pos, "expected either 1 or 2 identifiers")
			bad_stmt = true
		}
	}

	in_tok = expect_token(p, .In)

	prev_allow_range := p.allow_range
	prev_level := p.expr_level
	p.allow_range = true
	p.expr_level = -1

	expr = parse_expr(p, false)

	p.expr_level = prev_level
	p.allow_range = prev_allow_range

	if allow_token(p, .Do) {
		body = convert_stmt_to_body(p, parse_stmt(p))
		if for_tok.pos.line != body.pos.line {
			error(p, body.pos, "the body of a 'do' must be on the same line as the 'for' token")
		}
	} else {
		body = parse_block_stmt(p, false)
	}

	if bad_stmt {
		return ast.new(ast.Bad_Stmt, inline_tok.pos, end_pos(p.prev_tok))
	}

	range_stmt := ast.new(ast.Inline_Range_Stmt, inline_tok.pos, body)
	range_stmt.unroll_pos = inline_tok.pos
	range_stmt.args = args[:]
	range_stmt.for_pos = for_tok.pos
	range_stmt.val0 = val0
	range_stmt.val1 = val1
	range_stmt.in_pos = in_tok.pos
	range_stmt.expr = expr
	range_stmt.body = body
	return range_stmt
}

parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
	#partial switch p.curr_tok.kind {
	case .Inline:
		if peek_token_kind(p, .For) {
			inline_tok := expect_token(p, .Inline)
			return parse_unrolled_for_loop(p, inline_tok)
		}
		fallthrough
	// Operands
	case .No_Inline,
	     .Context, // Also allows for 'context = '
	     .Proc,
	     .Ident,
	     .Integer, .Float, .Imag,
	     .Rune, .String,
	     .Open_Paren,
	     .Pointer,
	     .Asm, // Inline assembly
	     // Unary Expressions
	     .Add, .Sub, .Xor, .Not, .And:

		s := parse_simple_stmt(p, {Stmt_Allow_Flag.Label})
		expect_semicolon(p, s)
		return s


	case .Foreign: return parse_foreign_decl(p)
	case .Import:  return parse_import_decl(p)
	case .If:      return parse_if_stmt(p)
	case .When:    return parse_when_stmt(p)
	case .For:     return parse_for_stmt(p)
	case .Switch:  return parse_switch_stmt(p)

	case .Defer:
		tok := advance_token(p)
		stmt := parse_stmt(p)
		#partial switch s in stmt.derived_stmt {
		case ^ast.Empty_Stmt:
			error(p, s.pos, "empty statement after defer (e.g. ';')")
		case ^ast.Defer_Stmt:
			error(p, s.pos, "you cannot defer a defer statement")
			stmt = s.stmt
		case ^ast.Return_Stmt:
			error(p, s.pos, "you cannot defer a return statement")
		}
		ds := ast.new(ast.Defer_Stmt, tok.pos, stmt)
		ds.stmt = stmt
		return ds

	case .Return:
		tok := advance_token(p)

		if p.expr_level > 0 {
			error(p, tok.pos, "you cannot use a return statement within an expression")
		}

		results: [dynamic]^ast.Expr
		for p.curr_tok.kind != .Semicolon && p.curr_tok.kind != .Close_Brace {
			result := parse_expr(p, false)
			append(&results, result)
			if p.curr_tok.kind != .Comma ||
			   p.curr_tok.kind == .EOF {
				break
			}
			advance_token(p)
		}

		end := end_pos(tok)
		if len(results) > 0 {
			end = results[len(results)-1].end
		}

		rs := ast.new(ast.Return_Stmt, tok.pos, end)
		rs.results = results[:]
		expect_semicolon(p, rs)
		return rs

	case .Break, .Continue, .Fallthrough:
		tok := advance_token(p)
		label: ^ast.Ident
		if tok.kind != .Fallthrough && p.curr_tok.kind == .Ident {
			label = parse_ident(p)
		}
		s := ast.new(ast.Branch_Stmt, tok.pos, label)
		s.tok = tok
		s.label = label
		expect_semicolon(p, s)
		return s

	case .Using:
		docs := p.lead_comment
		tok := expect_token(p, .Using)

		if p.curr_tok.kind == .Import {
			return parse_import_decl(p, Import_Decl_Kind.Using)
		}

		list := parse_lhs_expr_list(p)
		if len(list) == 0 {
			error(p, tok.pos, "illegal use of 'using' statement")
			expect_semicolon(p, nil)
			return ast.new(ast.Bad_Stmt, tok.pos, end_pos(p.prev_tok))
		}

		if p.curr_tok.kind != .Colon {
			end := list[len(list)-1]
			expect_semicolon(p, end)
			us := ast.new(ast.Using_Stmt, tok.pos, end)
			us.list = list
			return us
		}
		expect_token_after(p, .Colon, "identifier list")
		decl := parse_value_decl(p, list, docs)
		if decl != nil {
			#partial switch d in decl.derived_stmt {
			case ^ast.Value_Decl:
				d.is_using = true
				return decl
			}
		}

		error(p, tok.pos, "illegal use of 'using' statement")
		return ast.new(ast.Bad_Stmt, tok.pos, end_pos(p.prev_tok))

	case .At:
		docs := p.lead_comment
		tok := advance_token(p)
		return parse_attribute(p, tok, .Open_Paren, .Close_Paren, docs)

	case .Hash:
		tok := expect_token(p, .Hash)
		tag := expect_token(p, .Ident)
		name := tag.text

		switch name {
		case "bounds_check", "no_bounds_check":
			stmt := parse_stmt(p)
			switch name {
			case "bounds_check":
				stmt.state_flags += {.Bounds_Check}
			case "no_bounds_check":
				stmt.state_flags += {.No_Bounds_Check}
			}
			return stmt
		case "type_assert", "no_type_assert":
			stmt := parse_stmt(p)
			switch name {
			case "type_assert":
				stmt.state_flags += {.Type_Assert}
			case "no_type_assert":
				stmt.state_flags += {.No_Type_Assert}
			}
			return stmt
		case "partial":
			stmt := parse_stmt(p)
			#partial switch s in stmt.derived_stmt {
			case ^ast.Switch_Stmt:      s.partial = true
			case ^ast.Type_Switch_Stmt: s.partial = true
			case: error(p, stmt.pos, "#partial can only be applied to a switch statement")
			}
			return stmt
		case "assert", "panic":
			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(tag))
			bd.tok  = tok
			bd.name = name
			ce := parse_call_expr(p, bd)
			es := ast.new(ast.Expr_Stmt, ce.pos, ce)
			es.expr = ce
			return es

		case "force_inline", "force_no_inline":
			expr := parse_inlining_operand(p, true, tag)
			es := ast.new(ast.Expr_Stmt, expr.pos, expr)
			es.expr = expr
			return es
		case "unroll":
			return parse_unrolled_for_loop(p, tag)
		case "reverse":
			stmt := parse_stmt(p)

			if range, is_range := stmt.derived.(^ast.Range_Stmt); is_range {
				if range.reverse {
					error(p, range.pos, "#reverse already applied to a 'for in' statement")
				}
				range.reverse = true
			} else {
				error(p, stmt.pos, "#reverse can only be applied to a 'for in' statement")
			}
			return stmt
		case "include":
			error(p, tag.pos, "#include is not a valid import declaration kind. Did you meant 'import'?")
			return ast.new(ast.Bad_Stmt, tok.pos, end_pos(tag))
		case:
			stmt := parse_stmt(p)
			end := stmt.pos if stmt != nil else end_pos(tok)
			te := ast.new(ast.Tag_Stmt, tok.pos, end)
			te.op   = tok
			te.name = name
			te.stmt = stmt

			fix_advance_to_next_stmt(p)
			return te
		}
	case .Open_Brace:
		return parse_block_stmt(p, false)

	case .Semicolon:
		tok := advance_token(p)
		s := ast.new(ast.Empty_Stmt, tok.pos, end_pos(tok))
		return s
	}


	#partial switch p.curr_tok.kind {
	case .Else:
		token := expect_token(p, .Else)
		error(p, token.pos, "'else' unattached to an 'if' statement")
		#partial switch p.curr_tok.kind {
		case .If:
			return parse_if_stmt(p)
		case .When:
			return parse_when_stmt(p)
		case .Open_Brace:
			return parse_block_stmt(p, true)
		case .Do:
			expect_token(p, .Do)
			return convert_stmt_to_body(p, parse_stmt(p))
		case:
			fix_advance_to_next_stmt(p)
			return ast.new(ast.Bad_Stmt, token.pos, end_pos(p.curr_tok))
		}
	}


	tok := advance_token(p)
	error(p, tok.pos, "expected a statement, got %s", tokenizer.token_to_string(tok))
	fix_advance_to_next_stmt(p)
	s := ast.new(ast.Bad_Stmt, tok.pos, end_pos(tok))
	return s
}


token_precedence :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> int {
	#partial switch kind {
	case .Question, .If, .When, .Or_Else:
		return 1
	case .Ellipsis, .Range_Half, .Range_Full:
		if !p.allow_range {
			return 0
		}
		return 2
	case .Cmp_Or:
		return 3
	case .Cmp_And:
		return 4
	case .Cmp_Eq, .Not_Eq,
	     .Lt, .Gt,
	     .Lt_Eq, .Gt_Eq:
		return 5
	case .In, .Not_In:
		if p.expr_level < 0 && !p.allow_in_expr {
			return 0
		}
		fallthrough
	case .Add, .Sub, .Or, .Xor:
		return 6
	case .Mul, .Quo,
	     .Mod, .Mod_Mod,
	     .And, .And_Not,
	     .Shl, .Shr:
		return 7
	}
	return 0
}

parse_type_or_ident :: proc(p: ^Parser) -> ^ast.Expr {
	prev_allow_type := p.allow_type
	prev_expr_level := p.expr_level
	defer {
		p.allow_type = prev_allow_type
		p.expr_level = prev_expr_level
	}

	p.allow_type = true
	p.expr_level = -1

	lhs := true
	return parse_atom_expr(p, parse_operand(p, lhs), lhs)
}
parse_type :: proc(p: ^Parser) -> ^ast.Expr {
	type := parse_type_or_ident(p)
	if type == nil {
		error(p, p.curr_tok.pos, "expected a type")
		return ast.new(ast.Bad_Expr, p.curr_tok.pos, end_pos(p.curr_tok))
	}
	return type
}

parse_body :: proc(p: ^Parser) -> ^ast.Block_Stmt {
	prev_expr_level := p.expr_level
	defer p.expr_level = prev_expr_level

	p.expr_level = 0
	open := expect_token(p, .Open_Brace)
	stmts := parse_stmt_list(p)
	close := expect_token(p, .Close_Brace)

	bs := ast.new(ast.Block_Stmt, open.pos, end_pos(close))
	bs.open = open.pos
	bs.stmts = stmts
	bs.close = close.pos
	return bs
}

convert_stmt_to_body :: proc(p: ^Parser, stmt: ^ast.Stmt) -> ^ast.Stmt {
	#partial switch s in stmt.derived_stmt {
	case ^ast.Block_Stmt:
		error(p, stmt.pos, "expected a normal statement rather than a block statement")
		return stmt
	case ^ast.Empty_Stmt:
		error(p, stmt.pos, "expected a non-empty statement")
	}

	bs := ast.new(ast.Block_Stmt, stmt.pos, stmt)
	bs.open = stmt.pos
	bs.stmts = make([]^ast.Stmt, 1)
	bs.stmts[0] = stmt
	bs.close = stmt.end
	bs.uses_do = true
	return bs
}

new_ast_field :: proc(names: []^ast.Expr, type: ^ast.Expr, default_value: ^ast.Expr) -> ^ast.Field {
	pos, end: tokenizer.Pos

	if len(names) > 0 {
		pos = names[0].pos
		if default_value != nil {
			end = default_value.end
		} else if type != nil {
			end = type.end
		} else {
			end = names[len(names)-1].pos
		}
	} else {
		if type != nil {
			pos = type.pos
		} else if default_value != nil {
			pos = default_value.pos
		}

		if default_value != nil {
			end = default_value.end
		} else if type != nil {
			end = type.end
		}
	}

	field := ast.new(ast.Field, pos, end)
	field.names = names
	field.type  = type
	field.default_value = default_value
	return field
}

Expr_And_Flags :: struct {
	expr:  ^ast.Expr,
	flags: ast.Field_Flags,
}

convert_to_ident_list :: proc(p: ^Parser, list: []Expr_And_Flags, ignore_flags, allow_poly_names: bool) -> []^ast.Expr {
	idents := make([dynamic]^ast.Expr, 0, len(list))

	for ident, i in list {
		if !ignore_flags {
			if i != 0 {
				error(p, ident.expr.pos, "illegal use of prefixes in parameter list")
			}
		}

		id: ^ast.Expr = ident.expr

		#partial switch n in ident.expr.derived_expr {
		case ^ast.Ident:
		case ^ast.Bad_Expr:
		case ^ast.Poly_Type:
			if allow_poly_names {
				if n.specialization == nil {
					break
				} else {
					error(p, ident.expr.pos, "expected a polymorphic identifier without an specialization")
				}
			} else {
				error(p, ident.expr.pos, "expected a non-polymorphic identifier")
			}
		case:
			error(p, ident.expr.pos, "expected an identifier")
			id = ast.new(ast.Ident, ident.expr.pos, ident.expr.end)
		}

		append(&idents, id)
	}

	return idents[:]
}

is_token_field_prefix :: proc(p: ^Parser) -> ast.Field_Flag {
	#partial switch p.curr_tok.kind {
	case .EOF:
		return .Invalid
	case .Using:
		advance_token(p)
		return .Using
	case .Hash:
		tok: tokenizer.Token
		advance_token(p)
		tok = p.curr_tok
		advance_token(p)
		if tok.kind == .Ident {
			for kf in ast.field_hash_flag_strings {
				if kf.key == tok.text {
					return kf.flag
				}
			}
		}
		return .Unknown
	}
	return .Invalid
}

parse_field_prefixes :: proc(p: ^Parser) -> (flags: ast.Field_Flags) {
	counts: [len(ast.Field_Flag)]int

	for {
		kind := is_token_field_prefix(p)
		if kind == .Invalid {
			break
		}

		if kind == .Unknown {
			error(p, p.curr_tok.pos, "unknown prefix kind '#%s'", p.curr_tok.text)
			continue
		}

		counts[kind] += 1
	}

	for kind in ast.Field_Flag {
		count := counts[kind]
		if kind == .Invalid || kind == .Unknown {
			// Ignore
		} else {
			if count > 1 { error(p, p.curr_tok.pos, "multiple '%s' in this field list", ast.field_flag_strings[kind]) }
			if count > 0 { flags += {kind} }
		}
	}

	return
}

check_field_flag_prefixes :: proc(p: ^Parser, name_count: int, allowed_flags, set_flags: ast.Field_Flags) -> (flags: ast.Field_Flags) {
	flags = set_flags
	if name_count > 1 && .Using in flags {
		error(p, p.curr_tok.pos, "cannot apply 'using' to more than one of the same type")
		flags -= {.Using}
	}

	for flag in ast.Field_Flag {
		if flag not_in allowed_flags && flag in flags {
			#partial switch flag {
			case .Unknown, .Invalid:
				// ignore
			case .Tags, .Ellipsis, .Results, .Default_Parameters, .Typeid_Token:
				panic("Impossible prefixes")
			case:
				error(p, p.curr_tok.pos, "'%s' is not allowed within this field list", ast.field_flag_strings[flag])
			}
			flags -= {flag}
		}
	}

	return flags
}

parse_var_type :: proc(p: ^Parser, flags: ast.Field_Flags) -> ^ast.Expr {
	if .Ellipsis in flags && p.curr_tok.kind == .Ellipsis {
		tok := advance_token(p)
		type := parse_type_or_ident(p)
		if type == nil {
			error(p, tok.pos, "variadic field missing type after '..'")
			type = ast.new(ast.Bad_Expr, tok.pos, end_pos(tok))
		}
		e := ast.new(ast.Ellipsis, type.pos, type)
		e.tok = tok.kind
		e.expr = type
		return e
	}
	type: ^ast.Expr
	if .Typeid_Token in flags && p.curr_tok.kind == .Typeid {
		tok := expect_token(p, .Typeid)
		specialization: ^ast.Expr
		end := tok.pos
		if allow_token(p, .Quo) {
			specialization = parse_type(p)
			end = specialization.end
		}

		ti := ast.new(ast.Typeid_Type, tok.pos, end)
		ti.tok = tok.kind
		ti.specialization = specialization
		type = ti
	} else {
		type = parse_type(p)
	}

	return type
}

check_procedure_name_list :: proc(p: ^Parser, names: []^ast.Expr) -> bool {
	if len(names) == 0 {
		return false
	}

	_, first_is_polymorphic := names[0].derived.(^ast.Poly_Type)
	any_polymorphic_names := first_is_polymorphic

	for i := 1; i < len(names); i += 1 {
		name := names[i]

		if first_is_polymorphic {
			if _, ok := name.derived.(^ast.Poly_Type); ok {
				any_polymorphic_names = true
			} else {
				error(p, name.pos, "mixture of polymorphic and non-polymorphic identifiers")
				return any_polymorphic_names
			}
		} else {
			if _, ok := name.derived.(^ast.Poly_Type); ok {
				any_polymorphic_names = true
				error(p, name.pos, "mixture of polymorphic and non-polymorphic identifiers")
				return any_polymorphic_names
			} else {
				// Okay
			}
		}
	}

	return any_polymorphic_names
}

parse_ident_list :: proc(p: ^Parser, allow_poly_names: bool) -> []^ast.Expr {
	list: [dynamic]^ast.Expr

	for {
		if allow_poly_names && p.curr_tok.kind == .Dollar {
			tok := expect_token(p, .Dollar)
			ident := parse_ident(p)
			if is_blank_ident(ident) {
				error(p, ident.pos, "invalid polymorphic type definition with a blank identifier")
			}
			poly_name := ast.new(ast.Poly_Type, tok.pos, ident)
			poly_name.type = ident
			append(&list, poly_name)
		} else {
			ident := parse_ident(p)
			append(&list, ident)
		}
		if p.curr_tok.kind != .Comma ||
		   p.curr_tok.kind == .EOF {
			break
		}
		advance_token(p)
	}

	return list[:]
}



parse_field_list :: proc(p: ^Parser, follow: tokenizer.Token_Kind, allowed_flags: ast.Field_Flags) -> (field_list: ^ast.Field_List, total_name_count: int) {
	handle_field :: proc(p: ^Parser,
	                     seen_ellipsis: ^bool, fields: ^[dynamic]^ast.Field,
	                     docs: ^ast.Comment_Group,
	                     names: []^ast.Expr,
	                     allowed_flags, set_flags: ast.Field_Flags,
	                     ) -> bool {

		expect_field_separator :: proc(p: ^Parser, param: ^ast.Expr) -> bool {
			tok := p.curr_tok
			if allow_token(p, .Comma) {
				return true
			}
			if allow_token(p, .Semicolon) {
				if !tokenizer.is_newline(tok) {
					error(p, tok.pos, "expected a comma, got a semicolon")
				}
				return true
			}
			return false
		}
		is_type_ellipsis :: proc(type: ^ast.Expr) -> bool {
			if type == nil {
				return false
			}
			_, ok := type.derived.(^ast.Ellipsis)
			return ok
		}

		is_signature := (allowed_flags & ast.Field_Flags_Signature_Params) == ast.Field_Flags_Signature_Params

		any_polymorphic_names := check_procedure_name_list(p, names)
		flags := check_field_flag_prefixes(p, len(names), allowed_flags, set_flags)

		type:          ^ast.Expr
		default_value: ^ast.Expr
		tag: tokenizer.Token

		expect_token_after(p, .Colon, "field list")
		if p.curr_tok.kind != .Eq {
			type = parse_var_type(p, allowed_flags)
			tt := ast.unparen_expr(type)
			if is_signature && !any_polymorphic_names {
				if ti, ok := tt.derived.(^ast.Typeid_Type); ok && ti.specialization != nil {
					error(p, tt.pos, "specialization of typeid is not allowed without polymorphic names")
				}
			}
		}

		if allow_token(p, .Eq) {
			default_value = parse_expr(p, false)
			if .Default_Parameters not_in allowed_flags {
				error(p, p.curr_tok.pos, "default parameters are only allowed for procedures")
				default_value = nil
			}
		}

		if default_value != nil && len(names) > 1 {
			error(p, p.curr_tok.pos, "default parameters can only be applied to single values")
		}

		if allowed_flags == ast.Field_Flags_Struct && default_value != nil {
			error(p, default_value.pos, "default parameters are not allowed for structs")
			default_value = nil
		}

		if is_type_ellipsis(type) {
			if seen_ellipsis^ {
				error(p, type.pos, "extra variadic parameter after ellipsis")
			}
			seen_ellipsis^ = true
			if len(names) != 1 {
				error(p, type.pos, "variadic parameters can only have one field name")
			}
		} else if seen_ellipsis^ && default_value == nil {
			error(p, p.curr_tok.pos, "extra parameter after ellipsis without a default value")
		}

		if type != nil && default_value == nil {
			if p.curr_tok.kind == .String {
				tag = expect_token(p, .String)
				if .Tags not_in allowed_flags {
					error(p, tag.pos, "Field tags are only allowed within structures")
				}
			}
		}

		ok := expect_field_separator(p, type)

		field := new_ast_field(names, type, default_value)
		field.tag     = tag
		field.docs    = docs
		field.flags   = flags
		field.comment = p.line_comment
		append(fields, field)

		return ok
	}


	start_tok := p.curr_tok

	docs := p.lead_comment

	fields: [dynamic]^ast.Field

	list: [dynamic]Expr_And_Flags
	defer delete(list)

	seen_ellipsis := false

	allow_typeid_token := .Typeid_Token in allowed_flags
	allow_poly_names := allow_typeid_token

	for p.curr_tok.kind != follow &&
	    p.curr_tok.kind != .Colon &&
	    p.curr_tok.kind != .EOF {
		prefix_flags := parse_field_prefixes(p)
		param := parse_var_type(p, allowed_flags & {.Typeid_Token, .Ellipsis})
		if _, ok := param.derived.(^ast.Ellipsis); ok {
			if seen_ellipsis {
				error(p, param.pos, "extra variadic parameter after ellipsis")
			}
			seen_ellipsis = true
		} else if seen_ellipsis {
			error(p, param.pos, "extra parameter after ellipsis")
		}

		eaf := Expr_And_Flags{param, prefix_flags}
		append(&list, eaf)
		allow_token(p, .Comma) or_break
	}

	if p.curr_tok.kind != .Colon {
		for eaf in list {
			type := eaf.expr
			tok: tokenizer.Token
			tok.pos = type.pos
			if .Results not_in allowed_flags {
				tok.text = "_"
			}

			names := make([]^ast.Expr, 1)
			names[0] = ast.new(ast.Ident, tok.pos, end_pos(tok))
			#partial switch ident in names[0].derived_expr {
			case ^ast.Ident:
				ident.name = tok.text
			case:
				unreachable()
			}

			flags := check_field_flag_prefixes(p, len(list), allowed_flags, eaf.flags)

			field := new_ast_field(names, type, nil)
			field.docs    = docs
			field.flags   = flags
			field.comment = p.line_comment
			append(&fields, field)
		}
	} else {
		names := convert_to_ident_list(p, list[:], true, allow_poly_names)
		if len(names) == 0 {
			error(p, p.curr_tok.pos, "empty field declaration")
		}

		set_flags: ast.Field_Flags
		if len(list) > 0 {
			set_flags = list[0].flags
		}
		total_name_count += len(names)
		handle_field(p, &seen_ellipsis, &fields, docs, names, allowed_flags, set_flags)

		for p.curr_tok.kind != follow && p.curr_tok.kind != .EOF {
			docs = p.lead_comment
			set_flags = parse_field_prefixes(p)
			names = parse_ident_list(p, allow_poly_names)

			total_name_count += len(names)
			handle_field(p, &seen_ellipsis, &fields, docs, names, allowed_flags, set_flags) or_break
		}
	}

	field_list = ast.new(ast.Field_List, start_tok.pos, p.curr_tok.pos)
	field_list.list = fields[:]
	return
}


parse_results :: proc(p: ^Parser) -> (list: ^ast.Field_List, diverging: bool) {
	if !allow_token(p, .Arrow_Right) {
		return
	}

	if allow_token(p, .Not) {
		diverging = true
		return
	}

	prev_level := p.expr_level
	defer p.expr_level = prev_level

	if p.curr_tok.kind != .Open_Paren {
		type := parse_type(p)
		field := new_ast_field(nil, type, nil)

		list = ast.new(ast.Field_List, field.pos, field.end)
		list.list = make([]^ast.Field, 1)
		list.list[0] = field
		return
	}

	expect_token(p, .Open_Paren)
	list, _ = parse_field_list(p, .Close_Paren, ast.Field_Flags_Signature_Results)
	expect_token_after(p, .Close_Paren, "parameter list")
	return
}


string_to_calling_convention :: proc(s: string) -> ast.Proc_Calling_Convention {
	if s[0] != '"' && s[0] != '`' {
		return nil
	}
	if len(s) == 2 {
		return nil
	}
	return s
}

parse_proc_tags :: proc(p: ^Parser) -> (tags: ast.Proc_Tags) {
	for p.curr_tok.kind == .Hash {
		_ = expect_token(p, .Hash)
		ident := expect_token(p, .Ident)

		switch ident.text {
		case "bounds_check":    tags += {.Bounds_Check}
		case "no_bounds_check": tags += {.No_Bounds_Check}
		case "optional_ok":     tags += {.Optional_Ok}
		case "optional_allocator_error": tags += {.Optional_Allocator_Error}
		case:
		}
	}

	if .Bounds_Check in tags && .No_Bounds_Check in tags {
		p.err(p.curr_tok.pos, "#bounds_check and #no_bounds_check applied to the same procedure type")
	}

	return
}

parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type {
	cc: ast.Proc_Calling_Convention
	if p.curr_tok.kind == .String {
		str := expect_token(p, .String)
		cc = string_to_calling_convention(str.text)
		if cc == nil {
			error(p, str.pos, "unknown calling convention '%s'", str.text)
		}
	}

	if cc == nil && p.in_foreign_block {
		cc = .Foreign_Block_Default
	}

	expect_token(p, .Open_Paren)
	p.expr_level += 1
	params, _ := parse_field_list(p, .Close_Paren, ast.Field_Flags_Signature_Params)
	p.expr_level -= 1
	expect_closing_parentheses_of_field_list(p)
	results, diverging := parse_results(p)

	is_generic := false

	loop: for param in params.list {
		if param.type != nil {
			if _, ok := param.type.derived.(^ast.Poly_Type); ok {
				is_generic = true
				break loop
			}
			for name in param.names {
				if _, ok := name.derived.(^ast.Poly_Type); ok {
					is_generic = true
					break loop
				}
			}
		}
	}

	end := end_pos(p.prev_tok)
	pt := ast.new(ast.Proc_Type, tok.pos, end)
	pt.tok = tok
	pt.calling_convention = cc
	pt.params = params
	pt.results = results
	pt.diverging = diverging
	pt.generic = is_generic
	return pt
}

parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ast.Expr {
	expr := parse_unary_expr(p, lhs)

	pi := ast.Proc_Inlining.None
	#partial switch tok.kind {
	case .Inline:
		pi = .Inline
	case .No_Inline:
		pi = .No_Inline
	case .Ident:
		switch tok.text {
		case "force_inline":
			pi = .Inline
		case "force_no_inline":
			pi = .No_Inline
		}
	}

	if expr != nil {
		#partial switch e in ast.strip_or_return_expr(expr).derived_expr {
		case ^ast.Proc_Lit:
			if e.inlining != .None && e.inlining != pi {
				error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal")
			}
			e.inlining = pi
			return expr
		case ^ast.Call_Expr:
			if e.inlining != .None && e.inlining != pi {
				error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call")
			}
			e.inlining = pi
			return expr
		}
	}

	error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text)
	return ast.new(ast.Bad_Expr, tok.pos, expr)
}

parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
	#partial switch p.curr_tok.kind {
	case .Ident:
		return parse_ident(p)

	case .Undef:
		tok := expect_token(p, .Undef)
		undef := ast.new(ast.Undef, tok.pos, end_pos(tok))
		undef.tok = tok.kind
		return undef

	case .Context:
		tok := expect_token(p, .Context)
		ctx := ast.new(ast.Implicit, tok.pos, end_pos(tok))
		ctx.tok = tok
		return ctx

	case .Integer, .Float, .Imag,
	     .Rune, .String:
		tok := advance_token(p)
		bl := ast.new(ast.Basic_Lit, tok.pos, end_pos(tok))
		bl.tok = tok
		return bl

	case .Open_Brace:
		if !lhs {
			return parse_literal_value(p, nil)
		}

	case .Open_Paren:
		open := expect_token(p, .Open_Paren)
		p.expr_level += 1
		expr := parse_expr(p, false)
		skip_possible_newline(p)
		p.expr_level -= 1
		close := expect_token(p, .Close_Paren)

		pe := ast.new(ast.Paren_Expr, open.pos, end_pos(close))
		pe.open  = open.pos
		pe.expr  = expr
		pe.close = close.pos
		return pe

	case .Distinct:
		tok := advance_token(p)
		type := parse_type(p)
		dt := ast.new(ast.Distinct_Type, tok.pos, type)
		dt.tok  = tok.kind
		dt.type = type
		return dt

	case .Hash:
		tok := expect_token(p, .Hash)
		name := expect_token(p, .Ident)
		switch name.text {
		case "type":
			type := parse_type(p)
			hp := ast.new(ast.Helper_Type, tok.pos, type)
			hp.tok  = tok.kind
			hp.type = type
			return hp

		case "file", "directory", "line", "procedure", "caller_location":
			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
			bd.tok  = tok
			bd.name = name.text
			return bd

		case "caller_expression":
			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
			bd.tok  = tok
			bd.name = name.text

			if peek_token_kind(p, .Open_Paren) {
				return parse_call_expr(p, bd)
			}
			return bd

		case "location", "exists", "load", "load_directory", "load_hash", "hash", "assert", "panic", "defined", "config":
			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
			bd.tok  = tok
			bd.name = name.text
			return parse_call_expr(p, bd)

		case "soa":
			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
			bd.tok  = tok
			bd.name = name.text
			original_type := parse_type(p)
			type := ast.unparen_expr(original_type)
			#partial switch t in type.derived_expr {
			case ^ast.Array_Type:         t.tag = bd
			case ^ast.Dynamic_Array_Type: t.tag = bd
			case ^ast.Pointer_Type:       t.tag = bd
			case:
				error(p, original_type.pos, "expected an array or pointer type after #%s", name.text)
			}
			return original_type

		case "simd":
			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
			bd.tok  = tok
			bd.name = name.text
			original_type := parse_type(p)
			type := ast.unparen_expr(original_type)
			#partial switch t in type.derived_expr {
			case ^ast.Array_Type:         t.tag = bd
			case:
				error(p, original_type.pos, "expected an array type after #%s", name.text)
			}
			return original_type

		case "partial":
			tag := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
			tag.tok = tok
			tag.name = name.text
			original_expr := parse_expr(p, lhs)
			expr := ast.unparen_expr(original_expr)
			#partial switch t in expr.derived_expr {
			case ^ast.Comp_Lit:
				t.tag = tag
			case ^ast.Array_Type:
				t.tag = tag
				error(p, tok.pos, "#%s has been replaced with #sparse for non-contiguous enumerated array types", name.text)
			case:
				error(p, tok.pos, "expected a compound literal after #%s", name.text)

			}
			return original_expr

		case "sparse":
			tag := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
			tag.tok = tok
			tag.name = name.text
			original_type := parse_type(p)
			type := ast.unparen_expr(original_type)
			#partial switch t in type.derived_expr {
			case ^ast.Array_Type:
				t.tag = tag
			case:
				error(p, tok.pos, "expected an enumerated array type after #%s", name.text)

			}
			return original_type

		case "bounds_check", "no_bounds_check":
			operand := parse_expr(p, lhs)

			switch name.text {
			case "bounds_check":
				operand.state_flags += {.Bounds_Check}
				if .No_Bounds_Check in operand.state_flags {
					error(p, name.pos, "#bounds_check and #no_bounds_check cannot be applied together")
				}
			case "no_bounds_check":
				operand.state_flags += {.No_Bounds_Check}
				if .Bounds_Check in operand.state_flags {
					error(p, name.pos, "#bounds_check and #no_bounds_check cannot be applied together")
				}
			case: unimplemented()
			}
			return operand

		case "relative":
			tag := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
			tag.tok = tok
			tag.name = name.text

			tag_call := parse_call_expr(p, tag)
			type := parse_type(p)

			rt := ast.new(ast.Relative_Type, tok.pos, type)
			rt.tag = tag_call
			rt.type = type
			return rt

		case "force_inline", "force_no_inline":
			return parse_inlining_operand(p, lhs, name)
		case:
			expr := parse_expr(p, lhs)
			end := expr.pos if expr != nil else end_pos(tok)
			te := ast.new(ast.Tag_Expr, tok.pos, end)
			te.op   = tok
			te.name = name.text
			te.expr = expr
			return te
		}

	case .Inline, .No_Inline:
		tok := advance_token(p)
		return parse_inlining_operand(p, lhs, tok)

	case .Proc:
		tok := expect_token(p, .Proc)

		if p.curr_tok.kind == .Open_Brace {
			open := expect_token(p, .Open_Brace)

			args: [dynamic]^ast.Expr

			for p.curr_tok.kind != .Close_Brace &&
			    p.curr_tok.kind != .EOF {
				elem := parse_expr(p, false)
				append(&args, elem)

				allow_token(p, .Comma) or_break
			}

			close := expect_closing_brace_of_field_list(p)

			if len(args) == 0 {
				error(p, tok.pos, "expected at least 1 argument in procedure group")
			}

			pg := ast.new(ast.Proc_Group, tok.pos, end_pos(close))
			pg.tok   = tok
			pg.open  = open.pos
			pg.args  = args[:]
			pg.close = close.pos
			return pg
		}

		type := parse_proc_type(p, tok)
		tags: ast.Proc_Tags
		where_token: tokenizer.Token
		where_clauses: []^ast.Expr

		skip_possible_newline_for_literal(p)

		if p.curr_tok.kind == .Where {
			where_token = expect_token(p, .Where)
			prev_level := p.expr_level
			p.expr_level = -1
			where_clauses = parse_rhs_expr_list(p)
			p.expr_level = prev_level
		}
		tags = parse_proc_tags(p)
		type.tags = tags

		if p.allow_type && p.expr_level < 0 {
			if where_token.kind != .Invalid {
				error(p, where_token.pos, "'where' clauses are not allowed on procedure types")
			}
			return type
		}
		body: ^ast.Stmt

		skip_possible_newline_for_literal(p)

		if allow_token(p, .Undef) {
			body = nil
			if where_token.kind != .Invalid {
				error(p, where_token.pos, "'where' clauses are not allowed on procedure literals without a defined body (replaced with ---")
			}
		} else if p.curr_tok.kind == .Open_Brace {
			prev_proc := p.curr_proc
			p.curr_proc = type
			body = parse_body(p)
			p.curr_proc = prev_proc
		} else if allow_token(p, .Do) {
			prev_proc := p.curr_proc
			p.curr_proc = type
			body = convert_stmt_to_body(p, parse_stmt(p))
			p.curr_proc = prev_proc
			if type.pos.line != body.pos.line {
				error(p, body.pos, "the body of a 'do' must be on the same line as the signature")
			}
		} else {
			return type
		}

		pl := ast.new(ast.Proc_Lit, tok.pos, end_pos(p.prev_tok))
		pl.type = type
		pl.body = body
		pl.tags = tags
		pl.where_token = where_token
		pl.where_clauses = where_clauses
		return pl

	case .Dollar:
		tok := advance_token(p)
		type := parse_ident(p)
		end := type.end

		specialization: ^ast.Expr
		if allow_token(p, .Quo) {
			specialization = parse_type(p)
			end = specialization.pos
		}
		if is_blank_ident(type) {
			error(p, type.pos, "invalid polymorphic type definition with a blank identifier")
		}

		pt := ast.new(ast.Poly_Type, tok.pos, end)
		pt.dollar = tok.pos
		pt.type = type
		pt.specialization = specialization
		return pt

	case .Typeid:
		tok := advance_token(p)
		ti := ast.new(ast.Typeid_Type, tok.pos, end_pos(tok))
		ti.tok = tok.kind
		ti.specialization = nil
		return ti

	case .Pointer:
		tok := expect_token(p, .Pointer)
		elem := parse_type(p)
		ptr := ast.new(ast.Pointer_Type, tok.pos, elem)
		ptr.pointer = tok.pos
		ptr.elem = elem
		return ptr


	case .Open_Bracket:
		open := expect_token(p, .Open_Bracket)
		count: ^ast.Expr
		#partial switch p.curr_tok.kind {
		case .Pointer:
			tok := expect_token(p, .Pointer)
			close := expect_token(p, .Close_Bracket)
			elem := parse_type(p)
			t := ast.new(ast.Multi_Pointer_Type, open.pos, elem)
			t.open = open.pos
			t.pointer = tok.pos
			t.close = close.pos
			t.elem = elem
			return t
		case .Dynamic:
			tok := expect_token(p, .Dynamic)
			close := expect_token(p, .Close_Bracket)
			elem := parse_type(p)
			da := ast.new(ast.Dynamic_Array_Type, open.pos, elem)
			da.open = open.pos
			da.dynamic_pos = tok.pos
			da.close = close.pos
			da.elem = elem
			return da
		case .Question:
			tok := expect_token(p, .Question)
			q := ast.new(ast.Unary_Expr, tok.pos, end_pos(tok))
			q.op = tok
			count = q
		case:
			p.expr_level += 1
			count = parse_expr(p, false)
			p.expr_level -= 1
		case .Close_Bracket:
			// handle below
		}
		close := expect_token(p, .Close_Bracket)
		elem := parse_type(p)
		at := ast.new(ast.Array_Type, open.pos, elem)
		at.open  = open.pos
		at.len   = count
		at.close = close.pos
		at.elem  = elem
		return at

	case .Map:
		tok := expect_token(p, .Map)
		expect_token(p, .Open_Bracket)
		key := parse_type(p)
		expect_token(p, .Close_Bracket)
		value := parse_type(p)

		mt := ast.new(ast.Map_Type, tok.pos, value)
		mt.tok_pos = tok.pos
		mt.key = key
		mt.value = value
		return mt

	case .Struct:
		tok := expect_token(p, .Struct)

		poly_params:     ^ast.Field_List
		align:           ^ast.Expr
		min_field_align: ^ast.Expr
		max_field_align: ^ast.Expr
		is_packed:       bool
		is_raw_union:    bool
		is_no_copy:      bool
		is_all_or_none:  bool
		fields:          ^ast.Field_List
		name_count:      int

		if allow_token(p, .Open_Paren) {
			param_count: int
			poly_params, param_count = parse_field_list(p, .Close_Paren, ast.Field_Flags_Record_Poly_Params)
			if param_count == 0 {
				error(p, poly_params.pos, "expected at least 1 polymorphic parameter")
				poly_params = nil
			}
			expect_token_after(p, .Close_Paren, "parameter list")
		}

		prev_level := p.expr_level
		p.expr_level = -1
		for allow_token(p, .Hash) {
			tag := expect_token_after(p, .Ident, "#")
			switch tag.text {
			case "packed":
				if is_packed {
					error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
				}
				is_packed = true
			case "all_or_none":
				if is_all_or_none {
					error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
				}
				is_all_or_none = true
			case "align":
				if align != nil {
					error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
				}
				align = parse_expr(p, true)
			case "field_align":
				if min_field_align != nil {
					error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
				}
				warn(p, tag.pos, "#field_align has been deprecated in favour of #min_field_align")
				min_field_align = parse_expr(p, true)
			case "min_field_align":
				if min_field_align != nil {
					error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
				}
				min_field_align = parse_expr(p, true)
			case "max_field_align":
				if max_field_align != nil {
					error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
				}
				max_field_align = parse_expr(p, true)
			case "raw_union":
				if is_raw_union {
					error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
				}
				is_raw_union = true
			case "no_copy":
				if is_no_copy {
					error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
				}
				is_no_copy = true
			case:
				error(p, tag.pos, "invalid struct tag '#%s", tag.text)
			}
		}
		p.expr_level = prev_level

		if is_raw_union && is_packed {
			is_packed = false
			error(p, tok.pos, "'#raw_union' cannot also be '#packed")
		}

		if is_raw_union && is_all_or_none {
			is_all_or_none = false
			error(p, tok.pos, "'#raw_union' cannot also be '#all_or_none")
		}

		where_token: tokenizer.Token
		where_clauses: []^ast.Expr

		skip_possible_newline_for_literal(p)

		if p.curr_tok.kind == .Where {
			where_token = expect_token(p, .Where)
			where_prev_level := p.expr_level
			p.expr_level = -1
			where_clauses = parse_rhs_expr_list(p)
			p.expr_level = where_prev_level
		}

		skip_possible_newline_for_literal(p)
		expect_token(p, .Open_Brace)
		fields, name_count = parse_field_list(p, .Close_Brace, ast.Field_Flags_Struct)
		close := expect_closing_brace_of_field_list(p)

		st := ast.new(ast.Struct_Type, tok.pos, end_pos(close))
		st.poly_params       = poly_params
		st.align             = align
		st.min_field_align   = min_field_align
		st.max_field_align   = max_field_align
		st.is_packed         = is_packed
		st.is_raw_union      = is_raw_union
		st.is_no_copy        = is_no_copy
		st.is_all_or_none    = is_all_or_none
		st.fields            = fields
		st.name_count        = name_count
		st.where_token       = where_token
		st.where_clauses     = where_clauses
		return st

	case .Union:
		tok := expect_token(p, .Union)
		poly_params: ^ast.Field_List
		align:       ^ast.Expr
		is_no_nil:     bool
		is_shared_nil: bool

		if allow_token(p, .Open_Paren) {
			param_count: int
			poly_params, param_count = parse_field_list(p, .Close_Paren, ast.Field_Flags_Record_Poly_Params)
			if param_count == 0 {
				error(p, poly_params.pos, "expected at least 1 polymorphic parameter")
				poly_params = nil
			}
			expect_token_after(p, .Close_Paren, "parameter list")
		}

		prev_level := p.expr_level
		p.expr_level = -1
		for allow_token(p, .Hash) {
			tag := expect_token_after(p, .Ident, "#")
			switch tag.text {
			case "align":
				if align != nil {
					error(p, tag.pos, "duplicate union tag '#%s'", tag.text)
				}
				align = parse_expr(p, true)
			case "maybe":
				error(p, tag.pos, "#%s functionality has now been merged with standard 'union' functionality", tag.text)
			case "no_nil":
				if is_no_nil {
					error(p, tag.pos, "duplicate union tag '#%s'", tag.text)
				}
				is_no_nil = true
			case "shared_nil":
				if is_shared_nil {
					error(p, tag.pos, "duplicate union tag '#%s'", tag.text)
				}
				is_shared_nil = true
			case:
				error(p, tag.pos, "invalid union tag '#%s", tag.text)
			}
		}
		p.expr_level = prev_level

		if is_no_nil && is_shared_nil {
			error(p, p.curr_tok.pos, "#shared_nil and #no_nil cannot be applied together")
		}

		union_kind := ast.Union_Type_Kind.Normal
		switch {
		case is_no_nil:     union_kind = .no_nil
		case is_shared_nil: union_kind = .shared_nil
		}

		where_token: tokenizer.Token
		where_clauses: []^ast.Expr

		skip_possible_newline_for_literal(p)

		if p.curr_tok.kind == .Where {
			where_token = expect_token(p, .Where)
			where_prev_level := p.expr_level
			p.expr_level = -1
			where_clauses = parse_rhs_expr_list(p)
			p.expr_level = where_prev_level
		}


		skip_possible_newline_for_literal(p)
		expect_token_after(p, .Open_Brace, "union")

		variants: [dynamic]^ast.Expr
		for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
			type := parse_type(p)
			if _, ok := type.derived.(^ast.Bad_Expr); !ok {
				append(&variants, type)
			}
			allow_token(p, .Comma) or_break
		}

		close := expect_closing_brace_of_field_list(p)



		ut := ast.new(ast.Union_Type, tok.pos, end_pos(close))
		ut.poly_params   = poly_params
		ut.variants      = variants[:]
		ut.align         = align
		ut.where_token   = where_token
		ut.where_clauses = where_clauses
		ut.kind          = union_kind

		return ut

	case .Enum:
		tok := expect_token(p, .Enum)
		base_type: ^ast.Expr
		if p.curr_tok.kind != .Open_Brace {
			base_type = parse_type(p)
		}

		skip_possible_newline_for_literal(p)
		open := expect_token(p, .Open_Brace)
		fields := parse_elem_list(p)
		close := expect_closing_brace_of_field_list(p)

		et := ast.new(ast.Enum_Type, tok.pos, end_pos(close))
		et.base_type = base_type
		et.open = open.pos
		et.fields = fields
		et.close = close.pos
		return et

	case .Bit_Set:
		tok := expect_token(p, .Bit_Set)
		open := expect_token(p, .Open_Bracket)
		elem, underlying: ^ast.Expr

		prev_allow_range := p.allow_range
		p.allow_range = true
		elem = parse_expr(p, false)
		p.allow_range = prev_allow_range

		if allow_token(p, .Semicolon) {
			underlying = parse_type(p)
		}


		close := expect_token(p, .Close_Bracket)

		bst := ast.new(ast.Bit_Set_Type, tok.pos, end_pos(close))
		bst.tok_pos = tok.pos
		bst.open = open.pos
		bst.elem = elem
		bst.underlying = underlying
		bst.close = close.pos
		return bst
		
	case .Matrix:
		tok := expect_token(p, .Matrix)
		expect_token(p, .Open_Bracket)
		row_count := parse_expr(p, false)
		expect_token(p, .Comma)
		column_count := parse_expr(p, false)
		expect_token(p, .Close_Bracket)
		elem := parse_type(p)

		mt := ast.new(ast.Matrix_Type, tok.pos, elem)
		mt.tok_pos = tok.pos
		mt.row_count = row_count
		mt.column_count = column_count
		mt.elem = elem
		return mt
	
	case .Bit_Field:
		tok := expect_token(p, .Bit_Field)

		backing_type := parse_type_or_ident(p)
		if backing_type == nil {
			token := advance_token(p)
			error(p, token.pos, "Expected a backing type for a 'bit_field'")
		}

		skip_possible_newline_for_literal(p)
		open := expect_token_after(p, .Open_Brace, "bit_field")

		fields: [dynamic]^ast.Bit_Field_Field
		for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
			docs := p.lead_comment

			name := parse_ident(p)
			expect_token(p, .Colon)
			type := parse_type(p)
			expect_token(p, .Or)
			bit_size := parse_expr(p, true)

			tag: tokenizer.Token
			if p.curr_tok.kind == .String {
				tag = expect_token(p, .String)
			}
			ok := allow_token(p, .Comma)

			field := ast.new(ast.Bit_Field_Field, name.pos, bit_size)

			field.name     = name
			field.type     = type
			field.bit_size = bit_size
			field.tag      = tag
			field.docs     = docs
			field.comments = p.line_comment

			append(&fields, field)

			if !ok {
				break
			}
		}

		close := expect_closing_brace_of_field_list(p)

		bf := ast.new(ast.Bit_Field_Type, tok.pos, end_pos(close))

		bf.tok_pos      = tok.pos
		bf.backing_type = backing_type
		bf.open         = open.pos
		bf.fields       = fields[:]
		bf.close        = close.pos
		return bf

	case .Asm:
		tok := expect_token(p, .Asm)

		param_types: [dynamic]^ast.Expr
		return_type: ^ast.Expr
		if allow_token(p, .Open_Paren) {
			for p.curr_tok.kind != .Close_Paren && p.curr_tok.kind != .EOF {
				t := parse_type(p)
				append(&param_types, t)
				if p.curr_tok.kind != .Comma ||
				   p.curr_tok.kind == .EOF {
					break
				}
				advance_token(p)
			}
			expect_token(p, .Close_Paren)

			if allow_token(p, .Arrow_Right) {
				return_type = parse_type(p)
			}
		}

		has_side_effects := false
		is_align_stack := false
		dialect := ast.Inline_Asm_Dialect.Default
		for allow_token(p, .Hash) {
			if p.curr_tok.kind == .Ident {
				name := advance_token(p)
				switch name.text {
				case "side_effects":
					if has_side_effects {
						error(p, tok.pos, "duplicate directive on inline asm expression: '#side_effects'")
					}
					has_side_effects = true
				case "align_stack":
					if is_align_stack {
						error(p, tok.pos, "duplicate directive on inline asm expression: '#align_stack'")
					}
					is_align_stack = true
				case "att":
					if dialect == .ATT {
						error(p, tok.pos, "duplicate directive on inline asm expression: '#att'")
					} else if dialect != .Default {
						error(p, tok.pos, "conflicting asm dialects")
					} else {
						dialect = .ATT
					}
				case "intel":
					if dialect == .Intel {
						error(p, tok.pos, "duplicate directive on inline asm expression: '#intel'")
					} else if dialect != .Default {
						error(p, tok.pos, "conflicting asm dialects")
					} else {
						dialect = .Intel
					}
				}

			} else {
				error(p, p.curr_tok.pos, "expected an identifier after hash")
			}
		}

		skip_possible_newline_for_literal(p)
		open := expect_token(p, .Open_Brace)
		asm_string := parse_expr(p, false)
		expect_token(p, .Comma)
		constraints_string := parse_expr(p, false)
		allow_token(p, .Comma)
		close := expect_closing_brace_of_field_list(p)

		e := ast.new(ast.Inline_Asm_Expr, tok.pos, end_pos(close))
		e.tok                = tok
		e.param_types        = param_types[:]
		e.return_type        = return_type
		e.constraints_string = constraints_string
		e.has_side_effects   = has_side_effects
		e.is_align_stack     = is_align_stack
		e.dialect            = dialect
		e.open               = open.pos
		e.asm_string         = asm_string
		e.close              = close.pos

		return e

	}

	return nil
}

is_literal_type :: proc(expr: ^ast.Expr) -> bool {
	val := ast.unparen_expr(expr)
	if val == nil {
		return false
	}
	#partial switch _ in val.derived_expr {
	case ^ast.Bad_Expr,
		^ast.Ident,
		^ast.Selector_Expr,
		^ast.Array_Type,
		^ast.Struct_Type,
		^ast.Union_Type,
		^ast.Enum_Type,
		^ast.Dynamic_Array_Type,
		^ast.Map_Type,
		^ast.Bit_Set_Type,
		^ast.Matrix_Type,
		^ast.Call_Expr,
		^ast.Bit_Field_Type:
		return true
	}
	return false
}

parse_value :: proc(p: ^Parser) -> ^ast.Expr {
	if p.curr_tok.kind == .Open_Brace {
		return parse_literal_value(p, nil)
	}
	prev_allow_range := p.allow_range
	defer p.allow_range = prev_allow_range
	p.allow_range = true
	return parse_expr(p, false)
}

parse_elem_list :: proc(p: ^Parser) -> []^ast.Expr {
	elems: [dynamic]^ast.Expr

	for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
		elem := parse_value(p)
		if p.curr_tok.kind == .Eq {
			eq := expect_token(p, .Eq)
			value := parse_value(p)

			fv := ast.new(ast.Field_Value, elem.pos, value)
			fv.field = elem
			fv.sep   = eq.pos
			fv.value = value

			elem = fv
		}

		append(&elems, elem)

		allow_token(p, .Comma) or_break
	}

	return elems[:]
}

parse_literal_value :: proc(p: ^Parser, type: ^ast.Expr) -> ^ast.Comp_Lit {
	elems: []^ast.Expr
	open := expect_token(p, .Open_Brace)
	p.expr_level += 1
	if p.curr_tok.kind != .Close_Brace {
		elems = parse_elem_list(p)
	}
	p.expr_level -= 1

	skip_possible_newline(p)
	close := expect_closing_brace_of_field_list(p)

	pos := type.pos if type != nil else open.pos
	lit := ast.new(ast.Comp_Lit, pos, end_pos(close))
	lit.type  = type
	lit.open  = open.pos
	lit.elems = elems
	lit.close = close.pos
	return lit
}

parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr {
	args: [dynamic]^ast.Expr

	ellipsis: tokenizer.Token

	p.expr_level += 1
	open := expect_token(p, .Open_Paren)

	seen_ellipsis := false
	for p.curr_tok.kind != .Close_Paren &&
		p.curr_tok.kind != .EOF {

		if p.curr_tok.kind == .Comma {
			error(p, p.curr_tok.pos, "expected an expression not ,")
		} else if p.curr_tok.kind == .Eq {
			error(p, p.curr_tok.pos, "expected an expression not =")
		}

		prefix_ellipsis := false
		if p.curr_tok.kind == .Ellipsis {
			prefix_ellipsis = true
			ellipsis = expect_token(p, .Ellipsis)
		}

		arg := parse_expr(p, false)
		if p.curr_tok.kind == .Eq {
			eq := expect_token(p, .Eq)

			if prefix_ellipsis {
				error(p, ellipsis.pos, "'..' must be applied to value rather than a field name")
			}

			value := parse_value(p)
			fv := ast.new(ast.Field_Value, arg.pos, value)
			fv.field = arg
			fv.sep   = eq.pos
			fv.value = value

			arg = fv
		} else if seen_ellipsis {
			error(p, arg.pos, "Positional arguments are not allowed after '..'")
		}

		append(&args, arg)

		if ellipsis.pos.line != 0 {
			seen_ellipsis = true
		}

		allow_token(p, .Comma) or_break
	}

	close := expect_closing_token_of_field_list(p, .Close_Paren, "argument list")
	p.expr_level -= 1

	ce := ast.new(ast.Call_Expr, operand.pos, end_pos(close))
	ce.expr     = operand
	ce.open     = open.pos
	ce.args     = args[:]
	ce.ellipsis = ellipsis
	ce.close    = close.pos

	o := ast.unparen_expr(operand)
	if se, ok := o.derived.(^ast.Selector_Expr); ok && se.op.kind == .Arrow_Right {
		sce := ast.new(ast.Selector_Call_Expr, ce.pos, ce)
		sce.expr = o
		sce.call = ce
		return sce
	}

	return ce
}

empty_selector_expr :: proc(tok: tokenizer.Token, operand: ^ast.Expr) -> ^ast.Selector_Expr {
	field := ast.new(ast.Ident, tok.pos, end_pos(tok))
	field.name = ""

	sel := ast.new(ast.Selector_Expr, operand.pos, field)
	sel.expr  = operand
	sel.op = tok
	sel.field = field

	return sel
}

parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^ast.Expr) {
	operand = value
	if operand == nil {
		if p.allow_type {
			return nil
		}
		error(p, p.curr_tok.pos, "expected an operand")
		fix_advance_to_next_stmt(p)
		be := ast.new(ast.Bad_Expr, p.curr_tok.pos, end_pos(p.curr_tok))
		operand = be
	}

	loop := true
	is_lhs := lhs
	for loop {
		#partial switch p.curr_tok.kind {
		case:
			loop = false

		case .Open_Paren:
			operand = parse_call_expr(p, operand)

		case .Open_Bracket:
			prev_allow_range := p.allow_range
			defer p.allow_range = prev_allow_range
			p.allow_range = false

			indices: [2]^ast.Expr
			interval: tokenizer.Token
			is_slice_op := false

			p.expr_level += 1
			open := expect_token(p, .Open_Bracket)

			#partial switch p.curr_tok.kind {
			case .Colon, .Ellipsis, .Range_Half, .Range_Full:
				// NOTE(bill): Do not err yet
				break
			case:
				indices[0] = parse_expr(p, false)
			}

			#partial switch p.curr_tok.kind {
			case .Ellipsis, .Range_Half, .Range_Full:
				error(p, p.curr_tok.pos, "expected a colon, not a range")
				fallthrough
			case .Colon, .Comma/*matrix index*/:
				interval = advance_token(p)
				is_slice_op = true
				if p.curr_tok.kind != .Close_Bracket && p.curr_tok.kind != .EOF {
					indices[1] = parse_expr(p, false)
				}
			}

			close := expect_token(p, .Close_Bracket)
			p.expr_level -= 1

			if is_slice_op {
				if interval.kind == .Comma {
					if indices[0] == nil || indices[1] == nil {
						error(p, p.curr_tok.pos, "matrix index expressions require both row and column indices")
					}
					se := ast.new(ast.Matrix_Index_Expr, operand.pos, end_pos(close))
					se.expr = operand
					se.open = open.pos
					se.row_index = indices[0]
					se.column_index = indices[1]
					se.close = close.pos

					operand = se
				} else {
					se := ast.new(ast.Slice_Expr, operand.pos, end_pos(close))
					se.expr = operand
					se.open = open.pos
					se.low = indices[0]
					se.interval = interval
					se.high = indices[1]
					se.close = close.pos

					operand = se
				}
			} else {
				ie := ast.new(ast.Index_Expr, operand.pos, end_pos(close))
				ie.expr = operand
				ie.open = open.pos
				ie.index = indices[0]
				ie.close = close.pos

				operand = ie
			}


		case .Period:
			tok := expect_token(p, .Period)
			#partial switch p.curr_tok.kind {
			case .Ident:
				field := parse_ident(p)

				sel := ast.new(ast.Selector_Expr, operand.pos, field)
				sel.expr  = operand
				sel.op = tok
				sel.field = field

				operand = sel

			case .Open_Paren:
				open := expect_token(p, .Open_Paren)
				type := parse_type(p)
				close := expect_token(p, .Close_Paren)

				ta := ast.new(ast.Type_Assertion, operand.pos, end_pos(close))
				ta.expr  = operand
				ta.open  = open.pos
				ta.type  = type
				ta.close = close.pos

				operand = ta

			case .Question:
				question := expect_token(p, .Question)
				type := ast.new(ast.Unary_Expr, question.pos, end_pos(question))
				type.op = question
				type.expr = nil

				ta := ast.new(ast.Type_Assertion, operand.pos, type)
				ta.expr  = operand
				ta.type  = type

				operand = ta

			case:
				error(p, p.curr_tok.pos, "expected a selector")
				operand = empty_selector_expr(tok, operand)
			}

		case .Arrow_Right:
			tok := expect_token(p, .Arrow_Right)
			#partial switch p.curr_tok.kind {
			case .Ident:
				field := parse_ident(p)

				sel := ast.new(ast.Selector_Expr, operand.pos, field)
				sel.expr  = operand
				sel.op = tok
				sel.field = field

				operand = sel
			case:
				error(p, p.curr_tok.pos, "expected a selector")
				operand = empty_selector_expr(tok, operand)
			}

		case .Pointer:
			op := expect_token(p, .Pointer)
			deref := ast.new(ast.Deref_Expr, operand.pos, end_pos(op))
			deref.expr = operand
			deref.op   = op

			operand = deref

		case .Or_Return:
			token := expect_token(p, .Or_Return)
			oe := ast.new(ast.Or_Return_Expr, operand.pos, end_pos(token))
			oe.expr  = operand
			oe.token = token

			operand = oe

		case .Or_Break, .Or_Continue:
			token := advance_token(p)
			label: ^ast.Ident

			end := end_pos(token)
			if p.curr_tok.kind == .Ident {
				end = end_pos(p.curr_tok)
				label = parse_ident(p)
			}

			oe := ast.new(ast.Or_Branch_Expr, operand.pos, end)
			oe.expr  = operand
			oe.token = token
			oe.label = label

			operand = oe

		case .Open_Brace:
			if !is_lhs && is_literal_type(operand) && p.expr_level >= 0 {
				operand = parse_literal_value(p, operand)
			} else {
				loop = false
			}

		case .Increment, .Decrement:
			if !lhs {
				tok := advance_token(p)
				error(p, tok.pos, "postfix '%s' operator is not supported", tok.text)
			} else {
				loop = false
			}
		}

		is_lhs = false
	}

	return operand

}

parse_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
	return parse_binary_expr(p, lhs, 0+1)
}
parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
	#partial switch p.curr_tok.kind {
	case .Transmute, .Cast:
		tok := advance_token(p)
		open := expect_token(p, .Open_Paren)
		type := parse_type(p)
		close := expect_token(p, .Close_Paren)
		expr := parse_unary_expr(p, lhs)

		tc := ast.new(ast.Type_Cast, tok.pos, expr)
		tc.tok   = tok
		tc.open  = open.pos
		tc.type  = type
		tc.close = close.pos
		tc.expr  = expr
		return tc

	case .Auto_Cast:
		op := advance_token(p)
		expr := parse_unary_expr(p, lhs)

		ac := ast.new(ast.Auto_Cast, op.pos, expr)
		ac.op   = op
		ac.expr = expr
		return ac

	case .Add, .Sub,
	     .Not, .Xor,
	     .And:
		op := advance_token(p)
		expr := parse_unary_expr(p, lhs)
		
		ue := ast.new(ast.Unary_Expr, op.pos, expr)
		ue.op   = op
		ue.expr = expr
		return ue

	case .Increment, .Decrement:
		op := advance_token(p)
		error(p, op.pos, "unary '%s' operator is not supported", op.text)
		expr := parse_unary_expr(p, lhs)

		ue := ast.new(ast.Unary_Expr, op.pos, expr)
		ue.op   = op
		ue.expr = expr
		return ue

	case .Period:
		op := advance_token(p)
		field := parse_ident(p)
		ise := ast.new(ast.Implicit_Selector_Expr, op.pos, field)
		ise.field = field
		return ise

	}
	return parse_atom_expr(p, parse_operand(p, lhs), lhs)
}
parse_binary_expr :: proc(p: ^Parser, lhs: bool, prec_in: int) -> ^ast.Expr {
	start_pos := p.curr_tok.pos
	expr := parse_unary_expr(p, lhs)

	if expr == nil {
		return ast.new(ast.Bad_Expr, start_pos, end_pos(p.prev_tok))
	}

	for prec := token_precedence(p, p.curr_tok.kind); prec >= prec_in; prec -= 1 {
		loop: for {
			op := p.curr_tok
			op_prec := token_precedence(p, op.kind)
			if op_prec != prec {
				break loop
			}

			#partial switch op.kind {
			case .If, .When:
				if p.prev_tok.pos.line < op.pos.line {
					// NOTE(bill): Check to see if the `if` or `when` is on the same line of the `lhs` condition
					break loop
				}
			}

			expect_operator(p)

			#partial switch op.kind {
			case .Question:

				cond := expr
				x := parse_expr(p, lhs)
				colon := expect_token(p, .Colon)
				y := parse_expr(p, lhs)
				te := ast.new(ast.Ternary_If_Expr, expr.pos, end_pos(p.prev_tok))
				te.cond = cond
				te.op1  = op
				te.x    = x
				te.op2  = colon
				te.y    = y

				expr = te
			case .If:
				x := expr
				cond := parse_expr(p, lhs)
				else_tok := expect_token(p, .Else)
				y := parse_expr(p, lhs)
				te := ast.new(ast.Ternary_If_Expr, expr.pos, end_pos(p.prev_tok))
				te.x    = x
				te.op1  = op
				te.cond = cond
				te.op2  = else_tok
				te.y    = y

				expr = te
			case .When:
				x := expr
				cond := parse_expr(p, lhs)
				skip_possible_newline(p)
				else_tok := expect_token(p, .Else)
				y := parse_expr(p, lhs)
				te := ast.new(ast.Ternary_When_Expr, expr.pos, end_pos(p.prev_tok))
				te.x    = x
				te.op1  = op
				te.cond = cond
				te.op2  = else_tok
				te.y    = y

				expr = te
			case .Or_Else:
				x := expr
				y := parse_expr(p, lhs)
				oe := ast.new(ast.Or_Else_Expr, expr.pos, end_pos(p.prev_tok))
				oe.x     = x
				oe.token = op
				oe.y     = y

				expr = oe

			case:
				right := parse_binary_expr(p, false, prec+1)
				if right == nil {
					error(p, op.pos, "expected expression on the right-hand side of the binary operator")
				}
				be := ast.new(ast.Binary_Expr, expr.pos, end_pos(p.prev_tok))
				be.left  = expr
				be.op    = op
				be.right = right

				expr = be
			}
		}
	}

	return expr
}


parse_expr_list :: proc(p: ^Parser, lhs: bool) -> ([]^ast.Expr) {
	list: [dynamic]^ast.Expr
	for {
		expr := parse_expr(p, lhs)
		append(&list, expr)
		if p.curr_tok.kind != .Comma || p.curr_tok.kind == .EOF {
			break
		}
		advance_token(p)
	}

	return list[:]
}
parse_lhs_expr_list :: proc(p: ^Parser) -> []^ast.Expr {
	return parse_expr_list(p, true)
}
parse_rhs_expr_list :: proc(p: ^Parser) -> []^ast.Expr {
	return parse_expr_list(p, false)
}

parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt {
	start_tok := p.curr_tok
	docs := p.lead_comment

	lhs := parse_lhs_expr_list(p)
	op := p.curr_tok
	switch {
	case tokenizer.is_assignment_operator(op.kind):
		// if p.curr_proc == nil {
		// 	error(p, p.curr_tok.pos, "simple statements are not allowed at the file scope");
		// 	return ast.new(ast.Bad_Stmt, start_tok.pos, end_pos(p.curr_tok));
		// }
		advance_token(p)
		rhs := parse_rhs_expr_list(p)
		if len(rhs) == 0 {
			error(p, p.curr_tok.pos, "no right-hand side in assignment statement")
			return ast.new(ast.Bad_Stmt, start_tok.pos, end_pos(p.curr_tok))
		}
		stmt := ast.new(ast.Assign_Stmt, lhs[0].pos, rhs[len(rhs)-1])
		stmt.lhs = lhs
		stmt.op = op
		stmt.rhs = rhs
		return stmt

	case op.kind == .In:
		if .In in flags {
			allow_token(p, .In)
			prev_allow_range := p.allow_range
			p.allow_range = true
			expr := parse_expr(p, false)
			p.allow_range = prev_allow_range

			rhs := make([]^ast.Expr, 1)
			rhs[0] = expr

			stmt := ast.new(ast.Assign_Stmt, lhs[0].pos, rhs[len(rhs)-1])
			stmt.lhs = lhs
			stmt.op = op
			stmt.rhs = rhs
			return stmt
		}
	case op.kind == .Colon:
		expect_token_after(p, .Colon, "identifier list")
		if .Label in flags && len(lhs) == 1 {
			is_partial := false
			is_reverse := false

			partial_token: tokenizer.Token
			if p.curr_tok.kind == .Hash {
				name := peek_token(p)
				if name.kind == .Ident && name.text == "partial" &&
				   peek_token(p, 1).kind == .Switch {
					partial_token = expect_token(p, .Hash)
					expect_token(p, .Ident)
					is_partial = true
				} else if name.kind == .Ident && name.text == "reverse" &&
				          peek_token(p, 1).kind == .For {
					partial_token = expect_token(p, .Hash)
					expect_token(p, .Ident)
					is_reverse = true
				}
			}

			#partial switch p.curr_tok.kind {
			case .Open_Brace, .If, .For, .Switch:
				label := lhs[0]
				stmt := parse_stmt(p)

				if stmt != nil {
					#partial switch n in stmt.derived_stmt {
					case ^ast.Block_Stmt:       n.label = label
					case ^ast.If_Stmt:          n.label = label
					case ^ast.For_Stmt:         n.label = label
					case ^ast.Switch_Stmt:      n.label = label
					case ^ast.Type_Switch_Stmt: n.label = label
					case ^ast.Range_Stmt:	    n.label = label
					}

					if is_partial {
						#partial switch n in stmt.derived_stmt {
						case ^ast.Switch_Stmt:      n.partial = true
						case ^ast.Type_Switch_Stmt: n.partial = true
						case:
							error(p, partial_token.pos, "incorrect use of directive, use '%s: #partial switch'", partial_token.text)
						}
					}
					if is_reverse {
						#partial switch n in stmt.derived_stmt {
						case ^ast.Range_Stmt: n.reverse = true
						case:
							error(p, partial_token.pos, "incorrect use of directive, use '%s: #reverse for'", partial_token.text)
						}
					}
				}

				return stmt
			}
		}
		return parse_value_decl(p, lhs, docs)
	}

	if len(lhs) > 1 {
		error(p, op.pos, "expected 1 expression, got %d", len(lhs))
		return ast.new(ast.Bad_Stmt, start_tok.pos, end_pos(p.curr_tok))
	}

	#partial switch op.kind {
	case .Increment, .Decrement:
		advance_token(p)
		error(p, op.pos, "postfix '%s' statement is not supported", op.text)
	}

	es := ast.new(ast.Expr_Stmt, lhs[0].pos, lhs[0])
	es.expr = lhs[0]
	return es
}

parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Group) -> ^ast.Decl {
	is_mutable := true

	values: []^ast.Expr
	type := parse_type_or_ident(p)

	#partial switch p.curr_tok.kind {
	case .Eq, .Colon:
		sep := advance_token(p)
		is_mutable = sep.kind != .Colon

		values = parse_rhs_expr_list(p)
		if len(values) > len(names) {
			error(p, p.curr_tok.pos, "too many values on the right-hand side of the declaration")
		} else if len(values) < len(names) && !is_mutable {
			error(p, p.curr_tok.pos, "all constant declarations must be defined")
		} else if len(values) == 0 {
			error(p, p.curr_tok.pos, "expected an expression for this declaration")
		}
	}

	if is_mutable {
		if type == nil && len(values) == 0 {
			error(p, p.curr_tok.pos, "missing variable type or initialization")
			return ast.new(ast.Bad_Decl, names[0].pos, end_pos(p.curr_tok))
		}
	} else {
		if type == nil && len(values) == 0 && len(names) > 0 {
			error(p, p.curr_tok.pos, "missing constant value")
			return ast.new(ast.Bad_Decl, names[0].pos, end_pos(p.curr_tok))
		}
	}

	end := p.prev_tok

	if p.expr_level >= 0 {
		end: ^ast.Expr
		if !is_mutable && len(values) > 0 {
			end = values[len(values)-1]
		}
		if p.curr_tok.kind == .Close_Brace &&
		   p.curr_tok.pos.line == p.prev_tok.pos.line {

		} else {
			expect_semicolon(p, end)
		}
	}

	if p.curr_proc == nil {
		if len(values) > 0 && len(names) != len(values) {
			error(p, values[0].pos, "expected %d expressions on the right-hand side, got %d", len(names), len(values))
		}
	}

	decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(end))
	decl.docs = docs
	decl.names = names
	decl.type = type
	decl.values = values
	decl.is_mutable = is_mutable
	return decl
}


parse_import_decl :: proc(p: ^Parser, kind := Import_Decl_Kind.Standard) -> ^ast.Import_Decl {
	docs := p.lead_comment
	tok := expect_token(p, .Import)

	import_name: tokenizer.Token
	is_using := kind != Import_Decl_Kind.Standard

	#partial switch p.curr_tok.kind {
	case .Ident:
		import_name = advance_token(p)
	case:
		import_name.pos = p.curr_tok.pos
	}

	path := expect_token_after(p, .String, "import")

	decl := ast.new(ast.Import_Decl, tok.pos, end_pos(path))
	decl.docs       = docs
	decl.is_using   = is_using
	decl.import_tok = tok
	decl.name       = import_name
	decl.relpath    = path
	decl.fullpath   = path.text

	if p.curr_proc != nil {
		error(p, decl.pos, "import declarations cannot be used within a procedure, it must be done at the file scope")
	} else {
		append(&p.file.imports, decl)
	}
	expect_semicolon(p, decl)
	decl.comment = p.line_comment

	return decl
}
